ThymeleafをController以外の場所で使う方法

こんにちは。 エキサイト株式会社の三浦です。

JavaでSpring Bootを使っている時にHTMLを扱いたい場合、Thymeleafというテンプレートエンジンを使うことが多いのではないでしょうか。 多くの場合はControllerから返却するときに自動でテンプレートファイルを紐付ける方法で事足りると思いますが、まれにController以外の場所で明示的に呼び出して使いたい場合もあります。

今回は、その方法を紹介します。

Thymeleafとは

Thymeleafは、Java用のテンプレートエンジンです。

Thymeleafは、ウェブとスタンドアローンどちらの環境でも利用できる、モダンなサーバーサイドJavaテンプレートエンジンです。HTML、XMLJavaScriptCSS、さらにプレーンテキストも処理することができます。

例えばHTMLを扱いたい場合、変数部分以外をテンプレートファイルとして最初から作っておき、最後に変数部分をコード側から注入することで、コード内で文字列からHTMLを組み立てるよりも圧倒的に見通しの良いコードにすることができます。

<div xmlns:th="http://www.thymeleaf.org">
    <span th:text="${title}"></span>
    
    <span th:text="${story}"></span>
</div>

通常使う場合は、Controllerからの返却時に自動的にテンプレートファイルを紐付け、最終的にHTMLをレスポンスとして返す方法を取りますが、これだと例えばコード内で一部のHTMLのみを組み立てたい、ということができません。 ですが実は、一部の設定を変更することで、明示的にテンプレートファイルをController以外の場所から呼び出して使用することができるようになります。

明示的に呼び出す方法

まずは、テンプレートエンジンの設定をカスタマイズしてBean化します。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

@Configuration
public class SpringTemplateEngineConfig {
    /**
     * SpringTemplateのBean化
     * @return 設定済みSpringTemplate
     */
    @Bean
    public SpringTemplateEngine springTemplateEngine() {
        var resolver = new ClassLoaderTemplateResolver();

        // HTMLとして使用
        resolver.setTemplateMode(TemplateMode.HTML);

        // resources配下で、どんなプレフィックスのファイルを使うか
        resolver.setPrefix("templates/");
        // resources配下で、どんなサフィックスのファイルを使うか
        resolver.setSuffix(".html");

        // 文字列エンコード
        resolver.setCharacterEncoding("UTF-8");

        // キャッシュするかどうか
        resolver.setCacheable(true);

        var engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);

        return engine;
    }
}

設定できたら、使用します。

resources/templates/sample.html に以下を配置

<div xmlns:th="http://www.thymeleaf.org">
    <span th:text="${title}"></span>
    
    <span th:text="${story}"></span>
</div>
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

@Component
@RequiredArgsConstructor
public class SampleImpl implements Sample {
    private final SpringTemplateEngine springTemplateEngine;

    @Override
    public String sample() {
        var context = new Context();
        context.setVariable("title", "サンプルタイトル");
        context.setVariable("story", "サンプル本文");
        String html =  springTemplateEngine.process("sample", context);

        return html;
    }
}

これで、 sample() メソッドを実行することで、指定のHTMLを文字列で取得することができるようになります。

最後に

上記のサンプル程度なら文字列から組み立てても大して問題はないでしょうが、これが複雑になっていくに連れテンプレートファイルで分けることで見通しが相当良くなっていきます。 ぜひ使ってみていただけると幸いです。

なおこの方法を使うと、今度はControllerでの自動紐付けができなくなるそうなので、両方で使いたい場合はController用と明示的呼び出し用の2つ分を設定する必要があることを注意してください。

Pythonがインストールできない状態から機械学習ライブラリを使うまで

iXIT染谷です。
機械学習で遊んでみようと思ったのにPythonがインストールできなかったので、そこから機械学習でテキスト分類するところまでやってみようと思います。
今回は仮想環境でPythonのインストールをしたときのメモです。

環境

MacOS 10.15.7

経緯

自身が参加するプロジェクトはゲーム系のアプリで、ユーザーが意見を書き込む目安箱のような場所がアプリ内にあります。
しかし、そこに集められた意見をまとめて見る機会は少なく、充分に活用されているとは言い難い。

そこで簡易的な機械学習でも使って、そこに集まった意見を振り分けようと考えていました。
機械学習を行うなら先人が多いPythonがいいのでPythonをインストール。

(ハイパー初心者諸兄に優しい「Pythonの開発環境を用意しよう!(Mac)」)

問題発生

brew install を実行しようとしたところ書き込み権限がないと怒られました。
会社から借用しているPCなのでadmin権限がないんですかね。。。
前までPython使えていたのにど う し て。。

 Error: The following directories are not writable by your user

機械学習のライブラリはほとんどがPythonパッケージなので、Pythonがないと何もできません。
情シスの方に相談してみてもいいけど、トンチンカンなこと言ったら嫌なので代替案がないか模索。

Dockerで環境を作る

仮想環境なら権限必要ないのでは?と思い、Dockerで環境を作る記事を検索。

普段Pyhtonを使うときはJupyter Notebookを使って実行しているので、できれば今回も使いたい...!
というより、たくさんのファイル使ったり試行錯誤するならJupyter Notebookを使うのがかなり楽なので、使えないのは嫌です。
GUI大好き。

qiita.com

上記の記事を参考にしながら環境構築が無事完了。

DockerでPython環境を作るのではいけない理由

DockerでPython3環境自体作る記事がありましたが、それではJupyter Notebookは使えません。
(もしかしたら設定とかいじればいけるのかも...?)
Jupyter Notebookのインストール自体はそこからできますが、Jupyter Notebookはローカルから立ち上げを行なってしまうので、結局Docker(仮想環境)でPython環境を作った意味がなくなってしまいます。 今回はJupyter Notebookを使いたかったのでこちらを選びませんでした。 (一応Python3環境を作りたいという方はこちらが分かりやすかったのでぜひ)
dockerで簡易にpython3の環境を作ってみる - Qiita

次の予定

① ユーザーの意見のデータを取ってくる
② ローデータを眺めながらどんな分類にするか考える
③ fastText(機械学習ライブラリの1つ)を使ったテキストの分類を行う

Jetpack Composeにおける状態ホイスティング

こんにちは。エキサイト株式会社 Androidエンジニアの克です。

今回はJetpack Composeにおける状態ホイスティングの概念についてお話します。

Composeの状態とは

Composeの状態の分類として、Composeの内部で状態を持つ「ステートフルなCompose」と、Composeの内部には状態を持たない「ステートレスなCompose」の2種類が存在します。

例えば下記のようなComposeは、内部で状態を持っているためステートフルとなります。

@Composable
fun NameInput() {
    var name by remember { mutableStateOf("") }

    TextField(
        value = name,
        onValueChange = { name = it }
    )
}

ステートフルなComposeの短所

先に例示したようなステートフルなComposeには、下記のような短所があります。

  • 内部に状態を持っているので、外部から状態に干渉できない
  • 内部に状態を持っているので、テストが容易ではない
  • 状態は変数のため、どこで変化するのかを追う必要がある

例えば上記のNameInputでは、ボタンを押した時にTextFieldを空にしたいということはできません。(NameInput内で実装すれば可能ですが、それはこのComposeの責務を超えてしまいます)

そのため、基本的にはステートレスなComposeの方が取り回しがよく汎用性があります。

このステートフルなComposeをステートレスなComposeに変えるための手法が状態ホイスティングです。

ただし、ステートフルなComposeの短所は長所にも言い換えられます。

  • 内部に状態を持っているため、外部からはComposeの状態を気にする必要がない

このため、場合によってはあえてステートフルなComposeとして扱うという選択肢もあるということを留意しておきましょう。

状態ホイスティングとは

ホイスティング(Hoisting)とは「巻き上げ」という意味で、Composeの状態ホイスティングとはすなわち「Composeの状態を外部に巻き上げる」という意味となります。

これは「Comopseの内部には状態を持たない」ようにするのと同義であり、結果としてステートレスなComposeにすることができるというわけです。

上記のステートフルなComposeであるNameInputに状態ホイスティングを適用したものが下記になります。

@Composable
fun NameInput(name: String, onNameChange: (String) -> Unit) {
    TextField(
        value = name,
        onValueChange = onNameChange
    )
}

@Composable
fun NameScreen() {
    var name by remember { mutableStateOf("") }

    NameInput(
        name = name,
        onNameChange = { name = it }
    )
}

状態ホイスティングとは具体的には、Composeが使用する値およびComposeで発生するイベントを外部に巻き上げることを指します。

上記のコードにおいてはTextFieldに表示する valueが使用する値、TextFieldで発生する onValueChangeが発生するイベントです。

こうすることでステートフルなComposeにあった短所を解消することができました。

  • 内部に状態を持たないので、外部から状態を変更できる
  • 内部に状態を持たないので、テストが容易である
  • 状態は不変なため、変更されることはない

例えばボタンを押した時にTextFieldを空にしたい場合は下記のような実装になります。

@Composable
fun NameScreen() {
    var name by remember { mutableStateOf("") }

    NameInput(
        name = name,
        onNameChange = { name = it }
    )

    Button(onClick = { name = "" }) {
        Text(text = "削除")
    }
}

最後に

Jetpack Composeは宣言型UIの思想で作られているため、カプセル化や状態ホイスティングによって責務の分離が行いやすく、部品としての管理がしやすくなっています。

再利用性も高く、不具合の生まれる余地を減らしながら効率的に画面の構築を行うことができます。

既存のレイアウトxmlとは概念が大きく変わりはしますが、Jetpack Composeはこういった面でのメリットが大きいのでぜひ使っていきたいですね。

【IoT/Arduino】1.5m以内に人が来たらWebAPIを叩く仕組み (材料費1900円)

iXIT株式会社 岡崎です。

去年は短時間でシステム化できるようにノーコードを試して実戦投入しました。

今年は、システム化できる範囲を広げようとIoTを試し始めました。

概要

人感センサーで1.5m以内に人が来たことを検知したら、

WebAPIを叩くという仕組みを作ってみます。

 

人が廊下を通ったら、WebAPIを叩いて回数記録&閾値で通知する感じ。

 

今回はWebAPIを叩くところまで。

使い道

・一人暮らしの親が元気にしているかをリモートから自動で把握

・防犯として、夜間に人が通たら通知

材料

追加購入した物 合計1,870円(税込み)

 ①人感センサー M5Stack用PIRセンサユニット(583 円)

  (赤外線の変化によって人の接近を検知するセンサー。

   1.5m以内に人が来たら検知します。)

 ②マイコンボード ATOM Lite (1,287 円)

  (CPU 240MHz×2、メモリ0.5MB、フラッシュメモリ4MB、Wi-FiBluetooth

 f:id:ixit-okazaki:20210719131411j:plain

手元にあったもの

 ③何らかのWebAPI

  ※今回はノーコード(Bubble.io)で作ったLINEメッセージ送信のWabAPIを使います。

 ④USB-Cのケーブル、USB充電器

作り方

  1. ①②④をそれそれケーブルでつなげます
  2. 「②マイコンボード」にプログラムを書きこむ

 ざっくり以上です。

システム内の流れ

【人が廊下を通る】

  ↓ 赤外線

【①人感センサーが検知する】1.5m以内に人が近づいたら検知

  ↓ Goveケーブル

【②マイコンボードがWebAPIを叩く】

 ※デバッグ用に、検知したらLEDがからに変わるように設定

  ↓ Wifi

WIFIルーター

  ↓ インターネット

【③何らかのWebAPI】

 ※今回は、検知したらLINEメッセージを送ります。   

ここまで動かした動画 (10秒)

youtu.be

プログラム

マイコンボードのプログラムは、C言語風の「Arduino言語」という言語で書きました。

 ①マイコンボードの初期化処理(setup)

 ②マイコンボードが起動中にループ実行する処理(loop)

 ③WebAPI呼び出し(send_line_message)

 ④マイコンボードのLEDの色変更(display_LED)

※【 】の中は環境によって変更

#include "M5Atom.h"
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

WiFiMulti wifiMulti;

uint8_t DisBuff[2 + 5 * 5 * 3];

// ①マイコンボードの初期化処理
void setup()
{
    M5.begin(true, false, true);

    delay(10);
    display_LED(0x00, 0x00, 0x00);
    
    //人感センサーと接続
    pinMode(32, INPUT);
    
    //Wifi接続
    wifiMulti.addAP("【SSID】", "【PASSWORD】");
}

// ②マイコンボードが起動中にループ実行する処理
void loop()
{

    if(digitalRead(32)==1){
        //人感センサーが検知中は、LEDを赤に変更
        display_LED(0xff, 0x00, 0x00);

        //WebAPIを呼び出し(今回はLINEメッセージ送信)
        send_line_message();

        delay(10000);

    }else{
        //人感センサーが検知していない場合、LEDを緑に変更
        display_LED(0x00, 0xff, 0x00);
    }
      
    delay(100);
    M5.update();
}

// ③WebAPI呼び出し
void send_line_message() {
    
    if((wifiMulti.run() == WL_CONNECTED)) {      
        //送信する情報を作成
        DynamicJsonDocument doc(JSON_OBJECT_SIZE(2));
        doc["uuid"] = "【LINEアカウントの内部ID】";
        doc["message"] = "廊下を通ったよ";
        String json;
        serializeJson(doc, json);

        // WebAPIを呼び出し
        HTTPClient http;
        http.begin("https://no-template01.bubbleapps.io/version-test/api/1.1/wf/line_send_mes/");
        http.addHeader("Content-Type", "application/json");
        http.POST(json);

        http.end();
        
        delay(50);  
     }  
     
}

// ④マイコンボードのLEDの色変更
void display_LED(uint8_t Rdata, uint8_t Gdata, uint8_t Bdata)
{
    DisBuff[0] = 0x05;
    DisBuff[1] = 0x05;
    for (int i = 0; i < 25; i++)
    {
        DisBuff[2 + i * 3 + 0] = Rdata;
        DisBuff[2 + i * 3 + 1] = Gdata;
        DisBuff[2 + i * 3 + 2] = Bdata;
    }

    M5.dis.displaybuff(DisBuff);
}

「Best Creator Award」と「審査員特別賞」をいただきました!

こんにちは、エキサイトでエンジニアをしている おおしげ(@_ohshige) です。
この度(4月の話ですが...)、大変光栄なことに、XTechグループ総会で「Best Creator Award」、部署の総会で「審査員特別賞」をいただいたので、ご紹介させてください。

昨年の話ではありますが、主にチーム内での技術力向上のために様々な取り組みを行ってきました。
例えば、輪読会や勉強会などの開催であったり、あらゆるプロジェクトのあらゆるPullRequestに首を突っ込んでは指摘したり、新規プロジェクトのあらゆる設計に携わったり、自動テストの推進であったり、色々やってきました。
光栄にもそのような取り組みが評価されて2つの賞をいただくことができました。

文字が少し読みづらいですが、画像の右側が「Best Creator Award」、左側が「審査員特別賞」のトロフィーです。
緊急事態宣言がずっと続いていて出社できなかったためこれらを受け取ることができず、その結果3ヶ月以上も経った今になってブログを書いています。
(トロフィーの画像をどうしても載せたかったんです!)

f:id:excite-ohshige:20210719103429j:plain:w500

Best Creator Award

今年の4月にXTechグループ総会がありました。
XTechグループ総会とはその名の通り、エキサイト・iXITだけでなくXTechグループに属するすべての社員が参加して行われる定期総会です。
今回のXTechグループ総会では、

  • MVP
  • Best Manager Award
  • Best Player Award
  • Best Creator Award
  • Best Project Award
  • Best Rookie Award

が発表されました。

そして、私おおしげは、その中でもエンジニアとデザイナーを対象に選ばれる「Best Creator Award」で表彰していただきました!
クリエイティブな業務を行っている中でまさにクリエイティブ向けの賞をいただけるのは、大変光栄なことでとてもうれしく思います!

サプライズ精神が強いのか全く知らされておらず、当日まで「XTechグループ総会か〜」といった程度のことしか頭にありませんでした。
しかも、私の受賞に向けてチームメンバーからのお祝いの言葉があり、事前に用意しておいたようで、チームで何も知らないのは自分だけの状態だったようです。
賞をいただいたこともチームメンバーからのお祝いの言葉もとても嬉しかったのですが、総会当日まで何も知らなかった自分が少し恥ずかしかったです。

また、実は、XTechグループ全体が受賞対象であるにも関わらず、「Best Project Award」には私の所属するチームが選ばれ、「Best Player Award」には私と同じ部署のメンバー(非技術職)が選ばれ、素晴らしい出来だったのかなと思います。
さらには、「Best Manager Award」にはエンジニアであり私と同期入社であるRadiotalk社CTOの斉藤が選ばれ、近しい人ばかりだなと思いました。

審査員特別賞

XTechグループ総会よりも少し前の4月に、部署全体での総会がありました。
1年に1回行われ、様々な発表があるのですが、そこでも表彰が行われます。
今年は、

  • MVP
  • 準MVP
  • 3位MVP
  • 審査員特別賞

が発表されました。

そして、私おおしげは、部署内での技術的な活動が認められ、「審査員特別賞」をいただきました!
昨年はノミネートに含まれていたものの表彰されるまでは至らずだったので、今年は表彰していただいて大変光栄でとてもありがたいです。
(時系列としては、この部署総会で受賞したあとにXTechグループ総会での受賞があったので、部署総会の時点では「このあとのことはまさに知る由もなく」という状態でした)

ちなみに、MVPに選ばれたのは、XTechグループ総会で「Best Player Award」に選ばれたメンバーでした。素晴らしい!

受賞にあたって

部署とグループ全体の両方で賞をいただけるのは、とても光栄であり、とてもありがたいことです。素直にめちゃくちゃ嬉しいです。
いずれの賞も、基本的にはチームとしての技術力向上のために行ってきた様々なコトや日々の取り組みを評価していただいた結果だと思います。
ですが、何をやってきたにしても独りよがりなことだけでは誰もついてきてくれませんし、ただの自己満足で終わってしまいます。
そういう点では、私がやりたいといったことを許可してくれた上長や、やりたいことについてきてくれたチームメンバーや、良かれと思って言っている指摘をウザい怖い先輩だと思わずに場合によっては素直に場合によっては反論した上で受け入れてくれた後輩たちなど、みんながいてくれた結果だと思っています。

「壮大な賞をとって十分に満足しました」みたいになってしまっていますが、そんなこともなく、日々精進していきたいと思っています。
XTechグループ総会ではMVPや連続でのCreator賞受賞を、部署総会ではMVPを、貪欲に狙っていこうと思います。

そして、その先では、私おおしげのチームでの技術力向上のための取り組みを行った結果、その中から次の「Best Creator Award」受賞メンバーが出てくれると、さらに冥利に尽きます。

ECSの定期ローリングアップデートをJenkinsでやった話

こんにちは。 エキサイト株式会社の三浦です。

突然ですが、ずーっと同じコンテナを使い続けていると問題がある、という状況になった方はいないでしょうか? 例えばメモリリークなどで、時間が経てば経つほどメモリ使用率が上がっていってしまい、最後にはパフォーマンスが出なくなってしまうなどです。

今回は、AWS ECSにてそれに対応する方法の1つとして、定期ローリングアップデートをJenkinsで行った話をしていきます。

ずっと同じコンテナを使い続けると起きる問題

アプリケーションの作り方の問題で、同じコンテナをずっと使い続けていると問題が発生する場合があります。 例えばメモリリークでメモリ使用率が際限なく上がってしまう、ゴミデータがたまり続けてストレージを圧迫してしまうなどです。

アプリケーションの修正によってそれらの問題に対応できるならそれで問題はないのですが、あまりに古いアプリケーションだったりして修正が困難な場合もあるでしょう。

弊社でも、あるアプリケーションでコンテナを更新せずにずっと使い続けるとメモリ使用率が上がっていってしまい、パフォーマンスが下がってしまうという問題が発生していました。 そこで弊社では、コンテナごと定期的に更新する方法で対処しました。

定期ローリングアップデート法

やり方は簡単で、Jenkins上で、定期的に対象ECSを強制ローリングアップデートするだけです。

ローリングアップデートは、AWS CLIを以下のように使用しました。

stages {
    stage("Rolling Update: ECS") {
        steps {
            // ローリングアップデート実行
            sh """
            aws ecs update-service \
                --cluster $CLUSTER_NAME \
                --service $SERVICE_NAME \
                --platform-version 1.4.0 \
                --force-new-deployment
            """

            // アップデートが完了するまで待つ
            sh """
            aws ecs wait services-stable  \
                --cluster $CLUSTER_NAME \
                --service $SERVICE_NAME
            """
        }
    }
}

それを、以下のように定期的にアップデートします。

H(0-30) 10 * * *

毎朝10:00~10:30の間で、よしなに実行してくれます。

さいごに

根本的な解決になっているとは言い難いかもしれませんが、とりあえずの処置としては十分に機能するのではないでしょうか。

state_notifierとfreezedのプロジェクトでMockitoを用いたunit test

エキサイト株式会社の高野です。
弊社では最近Flutterでテストを書き始めました。state_notifierとfreezedを用いたプロジェクトをどうやってテストしようか考えていた所Mockitoを見つけ、よさそうでしたので書いてみたコードの流れの紹介になります。

各バージョン

Flutter: 2.0.6
iOS: 14.5
Android: 11.0

使用ライブラリ

mockito

実装

僕が今開発しているローリエプレスにおける最も重要たるロジックはAPIから受け取ったものをView側に流してあげることです。
プロジェクト自体は、state_notifierとfreezedを用いて実装していますのでrepositoryから受け取ったものをstateに渡せているかをテストしていきます。

@GenerateMocks([RepositoryImpl])
void main() {
  TestWidgetsFlutterBinding.ensureInitialized();
  final _mockRepositoryImpl = MockRepositoryImpl();
  final controller = Controller(
      controller: controller,
   );
}

以上のようにRepositoryをMockitoを用いてモック化し、Controllerをイニシャライズします。

test('記事を20件取得する', () async {
      when(RepositoryImpl.fetchArticleList(category: Pickup().package, page: 1, size: 20))
          .thenAnswer((_) async =>
          Result.success(List.generate(20, (_) => Article())));
      await controller.initArticleList();
      expect(controller.state.maybeMap(
          success: (s) => s.articles.length,
          orElse: () {},
      ), 20);
      expect(pickupArticleListController.page, 1);
    });

つづいて以上のようにrepositoryのメソッドに必要な引数を設定し、Futureメソッドですので thenAnswer を用いて返り値を指定してあげます。今回ですとArticle型のListを要素20で生成しました。
その後メソッドを実行し、stateのarticlesの長さが20に変更されているかを確認します。
僕のプロジェクトですと、stateをsuccess, loading, errorの3つの状態で用意していますので今回はAPIがうまくいっているものを前提としているのでsuccessに通ってくるのでmaybeMapを使用しています。

まとめ

Futureメソッドで返り値がResultかつそれをstate.copyWithで変数を変更した場合のUnitTestの紹介でした。これを元に他のファイルもテストしていこうと思います。

www.wantedly.com

SlackにAPI経由でメッセージ・画像を送信するための権限設定

こんにちは。 エキサイト株式会社の三浦です。

Slackは、多くの人が使っているメッセージングアプリケーションです。 また、単に人間がメッセージを送るだけでなく、Slack APIを使うことでプログラムから自動でメッセージを送信することができます。

今回は、Slack APIからSlackにメッセージ・画像ファイルを送信する際に必要となるSlack APIの設定を説明していきます。 なおこちらは、2021年7月現在の方法となっていますので、あらかじめご了承ください。

Slack APIとは

Slack API は、使用することでSlackに対してプログラム等を通じて自動でメッセージやファイルの送信をすることができるようになる仕組みです。 弊社ではこの仕組みを使って、スプレッドシートにまとめているサービスの各種KPIをGoogle App Scriptを通じて毎朝ピックアップしてSlackに流すようにしており、ざっくりとしたサービスの数字把握に役立てています。

このSlack APIですが、使用する際に細かい権限設定をする必要があり、少し複雑です。

文字列・ファイルを送信するためのSlack APIの設定方法

まずは、 「アプリ作成」 画面に行きます。

f:id:excite-takayuki-miura:20210712103026p:plain

Create New App ボタンを押し、 From scratch を選びましょう。

f:id:excite-takayuki-miura:20210712153833p:plain

App Name には自由な名前を、 Pick a workspace to develop your app in では、Slack APIを使用したいworkspaceを選びます。

アプリの作成をすると、 Basic Information というページに飛びます。 ここから、今作成した App に必要な権限等を付与していくことになります。

権限の付与のために、 Permissions を選択しましょう。

f:id:excite-takayuki-miura:20210712112231p:plain

なお、 Incoming Webhooks でもSlackにメッセージを送信することはできますが、ファイルの送信はできないようです。 メッセージの送信だけなら構いませんが、もしファイルも送信したいのであれば、 Incoming Webhooks は選ばないようにしてください。

Permissions を選択すると、左ナビの OAuth & Permissions というページに飛びます。 下にスクロールしていくと、 Scopes という項目があるので、そこから権限を設定していきます。

Bot Token Scopes の権限を設定しましょう。

f:id:excite-takayuki-miura:20210712113526p:plain

メッセージ送信のために chat:write を、ファイルの送信のために files:write を選択します。

f:id:excite-takayuki-miura:20210712113625p:plain

選択後、一番上にスクロールすると Install to Workspace というボタンがクリック可能になっているので、クリックします。

f:id:excite-takayuki-miura:20210712113711p:plain

リクエストの許可を求めるページに遷移するので、許可をするとこのアプリケーションが使用可能になります。

許可をすると Bot User OAuth Token が表示されているはずです。 ただし、この時点ではまだメッセージやファイルを送信することはできません。

最後にSlack本体にて、Slack APIを使用したいチャンネルの「ショートカット」か、チャンネル詳細の「インテグレーション」から今回作ったアプリを追加してください。

f:id:excite-takayuki-miura:20210712114120p:plain

f:id:excite-takayuki-miura:20210712114303p:plain

これで準備が整いました。

Bot User OAuth Token を使った具体的なメッセージ・ファイル送信方法は今回は省略します。

こちら など、色々なところに情報が載っていると思いますので、そちらでご確認ください。

最後に

Slackの権限回りは、時間が経つと変わっている場合があります。 私も、昔やった設定方法とは異なっているような気がして少し戸惑いました。

この方法もいずれは違うやり方になっている可能性はありますが、それまでは役に立てば幸いです。

🎉第1回新卒ビジデザLT会を開催してみた話🎉

f:id:excite_ny:20210706121834p:plain

こんにちは。21卒デザイナーの山﨑です! 7/2に新卒ビジネス職&デザイナー職でLT会を開催しました🎉

まず最初にLT会とは何なのか?と言いますと…

LT=ライトニングトーク(英: Lightning Talks)とはカンファレンスやフォーラムなどで行われる短いプレゼンテーションのこと。 様々な形式があるが、持ち時間が5分という制約が広く共有されている。(wiki

LT会でのお題は基本自由なのですが、今回発表者に課されたテーマは

「配属した部署で学んだこと」でした!

当日の様子

当日はお寿司やピザなどみんなで食事をしながら楽しく進んで行きました!

f:id:excite_ny:20210706123317j:plain f:id:excite_ny:20210709102002j:plain

コロナの状況で同期となかなか会えない状況が続く中、新卒の2ヶ月間の成果を見ることができ、お互いの様子が確認しあえてよかったです。

f:id:excite_ny:20210709100318j:plain

プレゼン紹介

実際新卒はどんなプレゼン内容を発表したのか?ということで、私のプレゼン内容を一部ご紹介します!

f:id:excite_ny:20210709100847p:plain f:id:excite_ny:20210709122511p:plainf:id:excite_ny:20210709122518p:plainf:id:excite_ny:20210709100919p:plain

このように自分が2ヶ月間やってきた業務内容と共に、学んだこと・できるようになった事を発表していきました。

最後に

LT会を通じて同期やメンターさん達に成果を発表でき、とても有意義な時間を過ごすことができました!

エキサイトではデザイナー、フロントエンジニア、バックエンドエンジニア、アプリエンジニアを絶賛募集しております!長期インターンも歓迎していますので、興味があれば連絡いただければと思います🙇‍♀️

それではまた!

www.wantedly.com

AWS Elasticsearchのバージョンを上げたらエラーが頻発するようになった話

こんにちは。 エキサイト株式会社の三浦です。

今回は、最近実際に私の身に起きた非常に困った話、AWS Elasticsearchエラー問題について説明していきます。

なおこの件は、とりあえず現状はエラーは収まっているものの、100%原因が判明したとは言い切れないものとなっています。 あらかじめご了承ください。

始まり

私が携わっているサービスでは、コスト削減のためにElasticsearchに対してReserved Instanceを購入しています。 Reserved Instanceは、簡単に言ってしまえば一定期間の料金を前払いする代わりに割引してもらう、というものです。

先日、以前購入したReserved Instanceの前払い期間が終了したのですが、その頃ちょうど 6g系インスタンス が発表されました。

6g系インスタンスは、AWSからの情報によれば、

お客様は、現世代 (M5、C5、R5) の対応する x86 ベースのインスタンスと比較して、最大 38% 向上したインデックス作成スループット、50% 削減されたインデックス作成レイテンシー、30% 向上したクエリパフォーマンスの恩恵を受けることができます。

Amazon Elasticsearch Service Graviton2 インスタンスでは、前世代のインスタンスに比べて費用対効果が最大 44% 向上しています。

とのことなので、これを使わない手はありません。

6g系インスタンスは使用できるバージョンが一定以上のものでないといけないので、テスト環境で検証した後に本番環境のバージョンをアップし、その後無事6g系のインスタンスタイプに変更が完了しました。

これでコストも下がるしパフォーマンスも上がるしいい事づくし!と思っていました。

それがそう簡単なことではないとわかったのは、それからしばらく経ったあとのことです。

問題発生

ある日突然、サービスに対するリクエストがエラーになっているという通知が届きました。 急いで調査してみると、なんとElasticsearchからのレスポンスが不安定になっているようです。

Elasticsearchのインスタンスタイプの増強やm5系への変更、不要なリクエストの削除による負荷低減等色々なことを試してみましたが、それでも断続的にエラーが起きる状態が続きました。

問題の解決?

色々と試していく中で、Elasticsearchへのアクセスを最初に受け付けるElasticsearch用APIに問題があるのでは、という考えから、そのAPI(dockerコンテナとして運用しています)の1コンテナあたりのリクエスト数を減らしてスケールアウトするようにしてみたところ、エラーが起きなくなりました。

とはいえそれまではエラーは出ていなかったので、エラー原因の可能性としてはAPI自体の問題というよりも、

  1. バージョンを上げたことによるElasticsearch側の何かしらの処理の変化
  2. 実は色々あってElasticseach自体を一度立て直したが、その際に過去に立ち上げたElasticsearchのインスタンスと何かしらの設定が変わってしまっており、処理が変化

したことによってAPIの1コンテナあたりのリクエスト数の上限が変化した、などが考えられますが、正直に言うと原因が完全に解明できたとは言えず、お手上げ状態です。

最後に

迂闊なバージョンやインスタンスタイプの変更などは、一見問題なさそうでも後々問題を引き起こす可能性がある、ということを身にしみて感じました。 どうすれば防げるかというとなかなか難しい話ですが、心構えだけは持っておくとなにかあった時に冷静に対処できるかもしれません。

みなさんも、バージョンアップ等をするときは不測の事態にくれぐれも注意しましょう。

Jetpack Composeでネットワーク上の画像をUIに表示する

こんにちは。エキサイト株式会社 Androidエンジニアの克です。

今回はJetpack ComposeのUIで、URLから画像を表示する方法についてお話します。

現在、画像の読み込みに関しての機能はComposeには実装されていませんが、こういった「必要とされているがまだ利用することはできない機能」をサポートするライブラリ群としてAccompanistというものがあります。

こちらに画像表示のためのライブラリも存在しているので、今回はこれを利用していこうと思います。

※ 今回紹介する機能はいずれJetpack Compose本体に実装されることが想定されます。サポート状況については公式のドキュメントを参照してください。

使用するライブラリ

Composeで画像を表示するためのAccompanistライブラリとして、下記の2種類が存在します。

  • Coil
  • Glide

Glide

GlideのCompose用のライブラリです。 ただし、特に理由がない場合には後述するCoilを利用することが推奨されています。

Unless you have a specific requirement to use Glide, consider using Coil instead.

https://google.github.io/accompanist/glide/

Coil

CoilのCompose用のライブラリです。 Glideと比較すると、Coroutineのサポートが強くComposeと相性が良いという理由でこちらの利用が推奨されています。

https://google.github.io/accompanist/coil/

実装

URLから画像を読み込んでImageに表示するだけであれば、実装はとても簡単です。

// Coin
Image(
    painter = rememberCoilPainter("画像のURL"),
    contentDescription = null
)

// Glide
Image(
    painter = rememberGlidePainter("画像のURL"),
    contentDescription = null
)

他にも画像の加工やアニメーションなど、様々な機能がサポートされています。

公式のドキュメントを参考にしていろいろと試してみるとよいでしょう。

最後に

既存のView実装でできていたことはComposeでもできるようになっていくでしょう。

Composeはまだベータ版であり足りていない機能も多いですが、7月に安定版がリリースされる予定ですし徐々に機能も拡充されていくと思います。

扱いやすい面も多いため、Composeのこれからの発展が楽しみです。

WebPush機能を使ってみる

はじめましてtaanatsuです よろしくお願いいたします。

初記事ということで、今回はWebPush機能を作っていこうと思います。
それでは、やっていきましょうか。

流れ

  1. Push通知を許可するかの確認用リンク作成
  2. manifest.jsonの作成
  3. サービスワーカー用のJavaScriptを作成
  4. 公開鍵と秘密鍵を使ってサーバーキーを作成
  5. WebサーバからPush通知を送信できるようにする
  6. Push通知を無効化する

以上です

Push通知を許可するかの確認用リンク作成

まずはPush通知を受信する、ブラウザ用のコードを書いていきます。
以下のようなHTMLを作成します。

index.htmlとして保存します。

<!DOCTYPE html>
<html lang="ja">
<head>
   <title>WebPushテスト</title>

   <meta charset='utf-8'>
   <meta name='viewport' content='width=device-width,initial-scale=1'>

   <meta name="apple-mobile-web-app-capable" content="yes">
   <meta name="apple-mobile-web-app-status-bar-style" content="black">
   <meta name="apple-mobile-web-app-title" content="WebPusher">
   <link rel="apple-touch-icon" href="icon-152x152.png">

   <!-- ウェブアプリマニフェストの読み込み -->
   <link rel="manifest" href="manifest.json">

   <link rel='icon' type='image/png' href='favicon.png'>

    <script defer src='service-worker.js'></script>
    <script src='webpush.js'></script>
</head>

<body>

    <a href="javascript:allowWebPush()">WebPushを許可する</a>

</body>
</html>

manifest.jsonの作成

こちらも簡易的ではありますが、作成します。

{
    "name": "example webpush",
    "short_name": "webpush",
    "theme_color": "#44518d",
    "background_color": "#2e3659",
    "display": "standalone",
    "scope": "/",
    "start_url": "/"
}

サービスワーカー用のJavaScriptを作成

ここからが本番です。
サービスワーカーを利用して、プッシュ通知の機能をつけていきます。

以下のファイルをservice-worker.jsとして保存します。

/**
 * プッシュ通知を受け取ったときのイベント
 */
self.addEventListener('push', function (event) {
    const title = 'Push通知テスト';
    const options = {
        body: event.data.text(),  // サーバーからのメッセージ
        tag: title,               // タイトル
        icon: 'icon-512x512.png', // アイコン
        badge: 'icon-512x512.png' // アイコン
    };

    event.waitUntil(self.registration.showNotification(title, options));
});


/**
 * プッシュ通知をクリックしたときのイベント
 */
self.addEventListener('notificationclick', function (event) {
    event.notification.close();

    event.waitUntil(
        // push通知がクリックされたら開くページ
        clients.openWindow('https://localhost:8080')
    );
});

公開鍵と秘密鍵を使ってサーバーキーを作成

以下のサイト様で発行できます。
Public KeyPrivate Keyをコピペで持ってきます。

https://web-push-codelab.glitch.me/

これらの情報を用いてサーバーキーを作成します。
以下のコードをwebpush.jsとして保存します。
また、3行目のPUBLIC_KEYに上記サイトで取得したPublic Keyを当て込めてください。

/**
 * 共通変数
 */
const PUBLIC_KEY = 'ここに取得した「Public Key」を書く';


/**
 * サービスワーカーの登録
 */
 self.addEventListener('load', async () => {
    if ('serviceWorker' in navigator) {
        window.sw = await navigator.serviceWorker.register('/service-worker.js', {scope: '/'});
    }
});


/**
 * WebPushを許可する仕組み
 */
async function allowWebPush() {
    if ('Notification' in window) {
        let permission = Notification.permission;

        if (permission === 'denied') {
            alert('Push通知が拒否されているようです。ブラウザの設定からPush通知を有効化してください');
            return false;
        }

        if (permission === 'granted') {
            alert('すでにWebPushを許可済みです');
            return false;
        }
    }
    // 取得したPublicKeyを「UInt8Array」形式に変換する
    const applicationServerKey = urlB64ToUint8Array(PUBLIC_KEY);

    // push managerにサーバーキーを渡し、トークンを取得
    let subscription = undefined;
    try {
        subscription = await window.sw.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey
        });
    } catch (e) {
        alert('Push通知機能が拒否されたか、エラーが発生したか、iPhoneでの実行かが原因でPush通知は送信されません。');
        return false;
    }


    // 必要なトークンを変換して取得(これが重要!!!)
    const key = subscription.getKey('p256dh');
    const token = subscription.getKey('auth');
    const request = {
        endpoint: subscription.endpoint,
        publicKey: btoa(String.fromCharCode.apply(null, new Uint8Array(key))),
        authToken: btoa(String.fromCharCode.apply(null, new Uint8Array(token)))
    };

    console.log(request);
}



/**
 * トークンを変換するときに使うロジック
 * @param {*} base64String 
 */
function urlB64ToUint8Array (base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

ここまでで、フロントエンドの準備は完了です。
サーバ環境で実行してみます。

PHPがインストールされていれば、簡易サーバを簡単に立ち上げることができるのでそれを利用してみてもいいでしょう、

$ cd index.htmlがおいてあるディレクトリ
$ php -S localhost:8080

上記コマンドの場合、 http://localhost:8080 にアクセスするとページが表示されるかと思います。

「WebPushを許可する」を押してPush通知を許可してみます。
これでPush通知を受信する手はずは整いました。

ここでブラウザコンソールを見てみると、

  • endpoint
  • publicKey
  • authToken

の情報が表示されると思います。
これらはPush通知を送信する際に必要になりますので、適当な場所にコピペして残しておいてください。
webpush.jsの52〜54行目にこれらを表示するコードが仕込んであります)
なお、本来の使い方としては、これらはDBなどに保存しておき、PHPなどから利用します。

WebサーバからPush通知を送信できるようにする

ここからはPush通知を送信するお話。
PHPでサーバからブラウザに向けてPush通知を送信していきます。

まずは以下のライブラリをcomposerを利用して取得します。
https://github.com/web-push-libs/web-push-php

$ composer require minishlink/web-push

次に、Push通知を送信するコードを作成していきます。 SendPush.phpとして保存します。 コード中のキーなどをの情報は適宜変更してください。

<?php
require_once 'vendor/autoload.php';

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;

const VAPID_SUBJECT = 'ここにあなたのWebサイトのURL(http://localhost:8080/ など)';
const PUBLIC_KEY = '公開鍵( https://web-push-codelab.glitch.me/ で取得したもの )';
const PRIVATE_KEY = '秘密鍵( https://web-push-codelab.glitch.me/ で取得したもの )';

// push通知認証用のデータ
$subscription = Subscription::create([
    'endpoint' => 'ブラウザのコンソールで表示されていた「endpoint」',
    'publicKey' => 'ブラウザのコンソールで表示されていた「publicKey」',
    'authToken' => 'ブラウザのコンソールで表示されていた「authToken」',
]);

// ブラウザに認証させる
$auth = [
    'VAPID' => [
        'subject' => VAPID_SUBJECT,
        'publicKey' => PUBLIC_KEY,
        'privateKey' => PRIVATE_KEY,
    ]
];

$webPush = new WebPush($auth);

$report = $webPush->sendOneNotification(
    $subscription,
    'push通知の本文です'
);

$endpoint = $report->getRequest()->getUri()->__toString();

if ($report->isSuccess()) {
    echo '送信成功ヽ(=´▽`=)ノ';
} else {
    echo '送信失敗(´;ω;`)';
}

これで準備は完了です。
実行してみましょう。

$ php SendPush.php

これでブラウザにPush通知が表示されたと思います。

Push通知を無効化する

次はPush通知を無効化していきましょう。 webpush.jsに以下のコードを追加します。

/**
 * WebPushの許可を取り消す
 */
async function denyWebPush() {
    const subscription = await window.sw.pushManager.getSubscription();
    if (subscription) {
        // Push通知を許可している場合は許可を取り消す
        subscription.unsubscribe();
        const request = {
            endpoint: subscription.endpoint
        };

        // このendpointをキーに、DBから情報を消したりする
        console.log(request);
    }
}

HTMLの方には以下のコードを追記

<a href="javascript:denyWebPush()">WebPushを拒否する</a>

リンクをクリックするとブラウザコンソールにendpointが表示されると思います。
これでPush通知は無効化されました。

試しにPush通知を送信してみてください。

$ php SendPiush.php

すると送信失敗のメッセージが表示されると思います。

ブラウザコンソールに表示させたendpointは、Push通知の許可を行った際に発行されるendpointと同値になるはずです。
DBにPush通知の送信先情報を登録していた場合、このendpointの情報をもとにDBからデータを削除するのに使います。

以上となります。

終わりに

ブラウザでPush通知を行える時代が来ております。
お手軽にPush通知を試せ、サービスにもお手軽に組み込めそうで良い感じですね。(iPhoneは対応してくれませんが…)
極力コピペで作れるようにしておりますので、ぜひともお試しください。

今回はここまで。
それではまた次回お会いしましょう。

参考にさせていただいたサイト様

記事を書く上でお世話になりました。

AWS ECSにおけるログ保存コスト削減のススメ・S3圧縮編

こんにちは。 エキサイト株式会社の三浦です。

以前、こんな記事を書きました。

tech.excite.co.jp

端的に言えば、「AWS ECSのログを保存する時、Fluent Bitをログルータとして使用すればCloudWatch以外にもログを流すことができ、適切にログの流し先を選択すればコストを削減できる」という話でした。

今回は、その中の1つである「S3にログを流す方法」について、ログ保存時に圧縮を行うことで更にコストを削減できる、というお話です。

S3におけるログ保存の流れ

Fluent Bitを使ってS3にログを保存する場合、通常は

  1. コンテナのログをFluent Bitが収集する
  2. 一定時間経過、もしくは一定サイズまでログが溜まったら、S3へ送信

となります。

この時、何も指定をしなければそのままS3へログが送られるのですが、圧縮して送ることができれば、送信するファイルサイズ・及びS3に保存されるファイルサイズが減るため、コスト削減になるという寸法です。

圧縮方法

Fluent Bitの設定ファイルの [OUTPUT] セクションにおいて、以下の3つの点を修正することでファイルを圧縮してS3に送信することができるようになります。

[OUTPUT]
    Name s3
    ...
    s3_key_format /test-$UUID.gz
    compression gzip
    content_type application/gzip

s3_key_format$UUID 変数と gz 拡張子をつける

S3へ出力するときのパス・オブジェクト名となる s3_key_format に、 $UUID 変数と gz 拡張子をつけます。

gz 拡張子をつけないと、S3上のファイルをAthena等で参照しようとする時にgzip圧縮されていると判定してくれないため、指定は必須です。 この時、明示的に $UUID 変数も入れておかないと、自動的にファイル名末尾にUUIDがついてしまい gz 拡張子を指定できないため、こちらの指定も必要になります。

compressiongzip を指定する

compressiongzip を指定することで、gzip圧縮してS3に送信してくれるようになります。

content_typeapplication/gzip を指定する

こちらは必須ではなく、指定せずとも問題はありませんが、正しい content_type を指定したほうが良いと思います。

圧縮結果

圧縮した結果、S3のファイルサイズは約1MBから約100KB程度まで下がりました。

f:id:excite-takayuki-miura:20210628142153p:plain
圧縮前

f:id:excite-takayuki-miura:20210628141804p:plain
圧縮後

さいごに

S3自体がもともと安価なため、よほどログ量が多くない限りは莫大なコスト削減になるわけではないですが、それでもストレージ料金や各種通信料金の削減にはなるかと思います。 なお注意点として、Fluent Bitのバージョンが古いと s3_key_format における $UUID 指定や compression の設定ができない可能性もあるため、新しいバージョンを使用してください。

MiroでオンラインKPTしてみた話

f:id:ixit-kata:20210624162559p:plain

こんにちは。エキサイトHD、iXIT株式会社の片岡です。

4月に育休から復帰したのですが、2年休業してる間に世の中はコロナ禍突入、テレワークメインになり、浦島太郎はついていくのに必死です(汗

 

先日、担当した短期プロジェクトのリリースが終わったので、振り返りでKPTをやろうとしたのですが、そういえば、オンラインでKPTってどうやってやるの?という疑問が。

太郎なりに調べたところ、どうやらMiroというオンラインホワイトボードが使いやすいらしい。WEBでも評判良いし、社内でもボチボチ使ってるところがあるみたい。ということで、ツールはMiro+Zoomを使ってみることに。

KPT準備

今までのオフラインKPTだと、事前に準備しておいてもらうことはあまりなかったのですが、今回は事前にMiroでボードを準備しておいて、KPTそれぞれの意見を前もって付箋で張っておいてもらうようメンバーにお願い。ボードは、「Start/Stop/Continue Retrospective」というテンプレートを使ってみました。

f:id:ixit-kata:20210623224327j:plain

振り返り用のテンプレらしい

 

 KPTメンバーは私入れて4名だったので、4名それぞれ、付箋で色をわけてみることに。Aさん黄色、Bさんはピンク、みたいな。いちいち付箋に名前書かなくても良いので楽だしわかりやすいです。

いざ、オンラインKPT

オンラインKPTの進め方はオフラインとほぼ同じです。違うのは、付箋を書いたり貼ったりする時間がないことくらい。

ただ、今回は、KPTのTが壮大になりそうな予感がしたので、直近の具体的なアクションに落とし込むActionというエリアも追加してみました。

zoom+Miroの共同編集で進行。zoom画面共有は使いません。

 

で、KPT後のアウトプットがこちら。ばばーん。

f:id:ixit-kata:20210624090104j:plain

画像が粗くてごめんなさい

左から、Keep/Problem/Try/Action。手書き箇所は、iPadのApplePencilで書いてます。

以下、MiroをKPTで使ってみた所感です。

メリット

①事前に意見出しができるので、議論に時間をたくさん使える 

KPT、まじめにやるとけっこう時間かかります。付箋書く時間が長くかかるのは仕方ないのですが、その付箋を順番に前に出て貼って発表という一連の動作のロスタイムも地味に多くて、ファシリテーターは時間気にしがち。それが事前に付箋書いて貼ってもらうだけで、こんなにゆっくり議論ができるとは。なんて素晴らしい!

 

 ②付箋の色で誰の意見や担当かすぐわかる

KPTの中で、付箋をカテゴリ分けしていくと、どれがだれの意見かわからなくなりがちですが、人で色分けしてると一目瞭然。オフラインでも付箋の色を変えることは可能ですが、人数多いとなかなか厳しく。営業、デザイン、運用、開発、いろいろな立場のメンバーが集まって開催されるKPTですが、俯瞰してボードを見ると、例えば、ナレッジ蓄積についての意見は運用や開発からが多い、KPIや横展は営業からの意見が多いなど、視点の違いがより明確になって面白いです。人数多いときは、職種によって付箋の色を変えても良いかもしれないですね。

Actionは、意見出しの際は白い付箋で書き出して、そのあと担当が決まったらその方の付箋の色に変更しました。こんな使い方できるのもオンラインならでは!

 

③手書きも使える

今回、PCとiPadで同じアカウントでログインしていたのですが、複数端末からまったく違和感なく同時操作できるので、PCメインで操作しながら、ちょっと手書きでメモしたいところはApplePencil。便利だし、使いどころ多そうです。

 

KPTを内容を残せる&共有できる

 リアルだと消してしまうホワイトボードも、Miroはそのまま残せるので、写真を何枚も撮ったり、議事録を書く手間がなく、ボードURLでチーム外への共有も簡単です。

デメリット

 ①事前に意見出しすることで、議論の中で生まれるアイデアが引き出しづらい

 KPTの良さのひとつは、色々な立場の人の意見を聞いて、新しいアイデアが出てくることです。それが事前に意見出しすることで、付箋を書く時間=考える時間が少なくなり、この種のアイデアが出づらくなってしまうなぁと。ファシリテーターは、この点踏まえたメンバーへの意見出しの働きかけが必要です。

 

うーん。デメリットは1個しか思いつきませんでした。

 まとめ

 やだ、なにこれ使いやすい。

リアルホワイトボード+付箋のKPTの欠点(終わったら消さなくてはいけない、付箋やペンの準備が必要、事前に準備しづらい)をいい感じに補ってくれて、振返りをもっと身近にしてくれる素敵ツール。

こんな世界があったのね!と太郎はちょっと感動。新しい価値観を与えてくれる玉手箱、ワクワクします。弊社もこんなプロダクトを世の中に出していきたいなぁ。がんばろう。

 

Miro、まだまだ使い込んでみて、良さげな使い方を見つけたらまたブログで報告しまーす。とりあえず社内にMiro布教するぞー。おーっ。

 

 

第1回定期勉強会「メディアのコーディング規約適用事例と効果」

会の様子
会の様子

エキサイトのあはれんです。

弊社で始まった社内定期勉強会について紹介いたします。 第1回定期勉強会は「メディアのコーディング規約適用事例と効果」でした。 メディアチームのコーディング規約について説明を聞き、エキサイトHDのメンバーで議論しました。 最大参加人数は51名にも達し、多くのメンバーが参加してくれました。😊🎉

コーディング規約を決めている定例会の議論の活性化方法から始まり、 命名規則等のコーディング規約について数多くのメンバーが意見を出し合っていました。

勉強になったポイント

今回の定期勉強会で、私個人が勉強になったことをピックアップして紹介します。

コーディング規約「早期returnを使用する」

私も早期returnを使用することには賛成でして、 「処理に関係ない条件の状態はさっさとreturnして、気にしないといけない条件の状態を減らしたい」 という気持ちでいつもreturnを使っています。

定期勉強会では、早期returnを使用する理由として以下が挙げられていました。

returnしないでいるとその後もダラダラ書けてしまい結合度が高くなる。 早期returnをすることでダラダラ書けない状態にする。

エンジニアに結合度を高くなるコードを書かせないようにする工夫として、 「早期return」を使うという観点はなかったので、 新たな視点で「早期return」を使う理由が知れてよかったです。

コーディング規約「単純分岐でSwitch文は使用しない」

これは、議論が盛り上がったコーディング規約でした。 その議論の中でも以下の意見には、ハッとさせられました。

switchだと、変数の状態によって処理が変わることがわかる。 ifの条件はなんでも書けるので、変数の状態によって処理が変わるかどうかはすぐに判断できない。

言われてみればそうなのですが、意識していなかった部分でした。 読みやすいコードを書く上で、上記の考え方は重要なことだと思いますので、 これからコードを書くときは意識してみようと思いました。

まとめ

コーディング規約に関して議論することで、コーディング規約の理由を理解することができてよかったです。 これからも社内の定期勉強会で得られた知見やノウハウを紹介していきますので、見ていていただけると幸いです。