IntelliJで共同編集 Code With Meを試してみました!

こんにちは、エキサイトの大津です。
以前から私が欲しいと思っていた機能がIntelliJであったので感動して投稿しました!

皆さんは、プログラミングで誰かに聞きたい場合や、教えるときにPCを持ってきて同じ画面を見てやりとりしていたことはありませんか?
そして相手のキーボードを打って『こうしたらいいんじゃない?』なんてやりとりをして、自分と違うキーボードや違う配列で戸惑い、自分の使っているPCで共同編集ができたらなんて楽なんだろうと。

最近、IntelliJのプラグインで『Code With Me』というものを知ってこれだ!となりました。

使い方も簡単で、まずはCode With Meをインストールします。

インストールすると、ツールバーに共有マークのような物が表示されますので、そこからURLを発行します。

f:id:excite-otsu:20210425194752p:plain
共有URLを発行する

あとはプロジェクトを共有したい人にURLをメッセージツールなどで共有します。
そしてアクセスしてきたときに承認してあげるだけです!

f:id:excite-otsu:20210425195047p:plain
共同編集中

わかりづらいですがカーソルが2つあるのが分かりますか? 同じファイルを編集するだけではなくそれぞれのファイルを編集しても構いません。

こちら、通話やビデオ通話機能、チャット機能や挙手などもついてます。

f:id:excite-otsu:20210425195301p:plain
ビデを通話中

環境に依存するかと思いますが通話の音質もビデオの遅延も少なかったです。
リモートワーク盛んなこのご時世、4月からの新卒入社の社員へのフォローやチーム開発での意識統一などにかなり役に立ちそうだと思いました!

※補足ですが8人程度で同一ファイルを編集したときは、補完機能などがうまく動かなかったのです。今後の発展に期待です。

では!

輪読会の開催が50回を超えました😊🎉

エキサイトのあはれんです。 2020年2月から社内の輪読会を開始して、2021年4月8日で開催回数が50回になりました。😊🎉 先週9冊目が読み終わり、次回から10冊目突入になります!🚀 今回は、社内で実施している輪読会について書きたいと思います。

輪読会の進め方

進めた方に関しては、輪読会の発起人である @_ohshige さんがすでにブログ記事で紹介していますので、それを引用させていただきます。

  1. 課題本を決める
  2. 1回の輪読会で取り扱える程度のまとまりの章で分割し、それぞれの担当を決める
  3. 輪読会当日までに全員がその範囲を読んでくる
  4. 輪読会当日は担当者がファシリテーターとなり良かった点や疑問点を提示して議論する
  5. 今の課題本が終わりに近づいたら次の課題本を決める

担当者が提示するものとして、章毎に以下のようなメモを用意しています。 担当者が、あらかじめエモポイント(良かった点)、気になること(疑問点)を書いてきて、 当日はメモと本を眺めながら議論し、その都度メモに追加していきます。

f:id:e125731:20210425174047p:plain
【輪読会】ユースケース駆動開発実践ガイド 8章のメモ

このメモは、議論内容のうっかり忘れ防止や、議論したことを振り返るときに役立っています。 その日の会を円滑に進めることが目的なので、見て分かるようにラフな内容になっています。 メモ等の前準備は省エネで押さえ、 その場の議論を大切にしている会なので、ここまで続けられたのかなと思っています。

私的!輪読会で良かったこと

私が輪読会をやって良かったと感じたことを書きたいと思います。 良かったことは以下の3つです。

  1. チームでの共通言語ができる
  2. 技術分野、経歴を超えた意見を聞ける
  3. 本を読み切れる

1. チームの共通言語ができる

お恥ずかしい話ですが、輪読会に参加するまでにドメイン駆動設計に関する知識が無く、 「ドメイン」「ユースケース」「リポジトリ」等の用語が分かっていませんでした。 輪読会でドメイン駆動設計に関する本を読み議論したことで理解でき、 ドメイン駆動設計を意識して開発するときは、より実装に近い議論をすることができるようになりました。 もし輪読会に参加していない世界線の私でしたら、ドメイン駆動設計の説明からになっていたので、 開発スピードを落としてまっていたかもしれません。

チームの技術知識レベルを底上げすることや、共通言語をつくる上で、 輪読会は効果的だと実感しています。

ユビキタス言語は、ドメイン駆動設計をする上でも大事ですもんね! (※ ユビキタス言語とは何か? ユビキタス言語とドメイン駆動設計に関連性については、「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」で紹介されています。)

2. 技術分野、経歴を超えた意見を聞ける

輪読会参加メンバーの専門技術(サーバーサイド、iOS、Android)、経歴(2〜8年目)がバラバラでしたので、 それぞれが経験してきた開発をもとにした意見を聞くことができ、 本の内容以上の知識を得ることができました。

3. 本を読み切れる

私は根っから締め切り駆動開発人間なので、締め切りがないとなかなか動くことができません...。 毎週「何章まで読んでくる」という締切を輪読会が提供してくれたおかげで、9冊の本を読めたと思います。 私と同じような締め切り駆動開発人間の方には、輪読会は特におすすめです。

おわりに

社内で開催している輪読会について書かせていただきました。 輪読会は、参加するメンバーや読む本によって方法は様々だと思います。 輪読会の一例として、これから開催する方のお役に少しでもなれたらと思います。

まだ読みたい本はたくさんありますので、これからも輪読会を続けていきたいと思います。🚀

参考 : 輪読会で読んだ本

  1. 『ドメイン駆動設計入門』
  2. 『オブジェクト指向でなぜつくるのか』
  3. 『現場で役立つシステム設計の原則』
  4. 『Clean Architecture』
  5. 『ドメイン駆動設計 モデリング/実装ガイド』
  6. 『事業をエンジニアリングする技術者たち』
  7. 『Design It !』
  8. 『レガシーコードからの脱却』
  9. 『ユースケース駆動開発実践ガイド』
  10. 『体系的に学ぶ 安全なWebアプリケーションの作り方』

MockHttpServletRequestでControllerのテストする(2)

エキサイト株式会社の中です。

Java Spring BootでControllerのJSONの送信と受信のテストをする方法を説明します。

ユースケース

  1. Controllerのテストをする。 2.受け口をPostにする。 3.Jsonパラメータを送信する 4.Jsonパラメータを受信する

題材

コード例

コントローラーです。

    @PostMapping("test")
    public String test(@RequestBody JsonBody jsonBody) {
        return jsonBody.getNaka();
    }

    @Data
    public static class JsonBody{
        private  String naka;
        private  String huga;
    }

入力例

Testクラスです。

    @Test
    public void testJsonParameters() {
        try {
            MockHttpServletRequestBuilder getRequest = MockMvcRequestBuilders.post("/test");
            final String contentAsString = this.mockMvc.perform(getRequest.with(request -> {
                final ObjectMapper objectMapper = new ObjectMapper();
                final Map<String, String> requestParameters = Map.of(
                        "naka","sho",
                        "huga","fuga"
                );
                try {
                    request.setContent(objectMapper.writeValueAsString(requestParameters).getBytes(StandardCharsets.UTF_8));
                    request.setContentType(MediaType.APPLICATION_JSON_VALUE);
                    return request;
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                    throw new RuntimeException();
                }
            }))
                    .andDo(MockMvcResultHandlers.print())
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn().getResponse().getContentAsString();

            assertEquals("sho", contentAsString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

出力例

テスト結果のコンソールです。

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /test
       Parameters = {}
          Headers = [Content-Type:"application/json"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.controller.TestController
           Method = com.controller.TestController#test(JsonBody)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=ISO-8859-1", Content-Length:"3"]
     Content type = text/plain;charset=ISO-8859-1
             Body = sho
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
Disconnected from the target VM, address: 'localhost:56988', transport: 'socket'
BUILD SUCCESSFUL in 50s
13 actionable tasks: 1 executed, 12 up-to-date

解説

        final ObjectMapper objectMapper = new ObjectMapper();
        final Map<String, String> requestParameters = Map.of(
                "naka","sho",
                "huga","fuga"
        );
        try {
            request.setContent(objectMapper.writeValueAsString(requestParameters).getBytes(StandardCharsets.UTF_8));
            request.setContentType(MediaType.APPLICATION_JSON_VALUE);
            return request;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }

Mapでjsonパラメータを作成し、コンテンツにbyteで設定します。 コンテンツタイプは明示的にapplication/jsonに設定します。

出力結果が正しく取れているので jsonでもテストができることがわかりました。

SpringBootでのリクエストの日付処理

エキサイト株式会社 メディア開発の佐々木です。

SpringBootでは、日付処理をある程度アノテーションで処理できるので共有します。

@RestController
@RequestMapping
public class DemoController {

    @GetMapping("/date_format")
    public Form dateFormat(Form form) {
        return form;
    }

    @Data
    static class Form {
        @DateTimeFormat(pattern = "yyyy-MM-dd")   // 入力時の期待フォーマット
        @JsonFormat(pattern = "yyyy/MM/dd")   // 出力時の期待フォーマット
        private LocalDate date;
    }
}

入力時に変換が必要であれば@DateTimeFormatを使用します。pattern = "yyyy-MM-dd" を定義してあげると、 2021-04-23みたいな日付の処理ができます。出力時に変換が必要であれば@JsonFormatを使います。 pattern = "yyyy/MM/dd"を定義してあげると、2021/04/23のように出力ができます。

試しに実行してみます。

$ curl http://localhost:8080/date_format?date=2020-12-12

{"date":"2020/12/12"}

期待どおりになりました。変換処理等も特に必要がないので手軽でいいです。

エキサイトでは、一緒に事業を運営していきたいエンジニアを募集しております。 インターン等も受け入れていますので、ぜひご連絡いただければと思います。

www.wantedly.com

カスタムバリデーション時のnullについて

エキサイト株式会社 メディア事業部エンジニアの中です。

カスタムバリデーションと@NotEmptyをつけてユニットテストする際、nullを設定すると NullPointerExceptionが発生することがあります。

理由は@NotEmptyと@CustomValidateの2回チェックが通るため、 CustomValidate側の方でnull対策をしておかないといけないからです。

ユースケース

データモデル

@Data
@Accessors(chain = true)
public class TestId {
    @NotEmpty
    @ExciteIdValidate
    private String id;
}

カスタムバリデーション

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {ExciteIdValidator.class})
public @interface ExciteIdValidate {

    String message() default "Idを正しく入力してください";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        ExciteIdValidate[] value();
    }
}
public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}

入力例

テストコードです

@ExtendWith(MockitoExtension.class)
class TestIdTest {

    private static Validator validator;

    @BeforeAll
    public static void BeforeAll() {
        validator = Validation
                        .buildDefaultValidatorFactory()
                        .getValidator();
    }

    @Test
    @Description("nullチェック")
    void ngNull() {
        final TestIdForm testId = new TestIdForm()
                .setId(null);
        assertFalse(validator.validate(testId).isEmpty());
    }
}

出力例 エラー部分抜粋しています。

Caused by: java.lang.NullPointerException
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:27)
    at com.form.validate.TestIdValidator.isValid(TestIdValidator.java:7)
    at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:180)
    ... 99 more

改善

当たり前のことですが、nullを考慮して実装すれば問題なく通ります。 returnをfalseにしていますが、trueでも最終的な結果はエラーになります。 @NotEmptyでエラー判定されているからです。

public class ExciteIdValidator implements ConstraintValidator<ExciteIdValidate, String> {

    private final static Pattern PATTERN_NG_WORD = Pattern.compile("ngword");

    @Override
    public void initialize(ExciteIdValidate exciteIdValidate) { }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(value == null){
            return false;
        }
        if(PATTERN_NG_WORD.matcher(value).matches()){
            return false;
        }

        return true;
    }
}

Android の Constraint Layout を複数扱うレイアウトはレンダリングパフォーマンスを下げる

エキサイト株式会社メディア事業部エンジニアの武藤です。

Android の ConstraintLayout を使ってレイアウトを実装する際に、レンダリングパフォーマンスに問題があるケースがあったので紹介します。

問題が起きたケース

開発が進んでレイアウトが複雑になってきたときに、リストページのスクロールにカクつきが出てきました。

その際のレイアウトの簡略版が下記です。

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <FrameLayout>

        <androidx.core.widget.NestedScrollView>

            <androidx.constraintlayout.widget.ConstraintLayout>

                <RecyclerView />

            </androidx.constraintlayout.widget.ConstraintLayout>

        </androidx.core.widget.NestedScrollView>

        <androidx.constraintlayout.widget.ConstraintLayout>
            
            <ProgressBar />

        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.constraintlayout.widget.ConstraintLayout>
                        
            <TextView
                android:id="@+id/text_error"
                ...
                />

            <Button
                android:id="@+id/button_reload"
                ...
                />
            
        </androidx.constraintlayout.widget.ConstraintLayout>

    </FrameLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

RecyclerView を使ったリストに、 API との通信中に表示する ProgressBar、通信エラー時にメッセージを出す TextView, 再読み込みをする Button を別々の ConstraintLayout の中で配置していました。

スペックの低い端末でわずかにカクついており、実装中には気づきにくいものでした。

改善したレイアウト

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    <androidx.constraintlayout.widget.ConstraintLayout>

        <RecyclerView />

        <ProgressBar />

        <androidx.constraintlayout.widget.Group
            app:constraint_referenced_ids="text_error, button_reload" />

        <TextView
            android:id="@+id/text_error"
            ...
            />

        <Button
            android:id="@+id/button_reload"
            ...
            />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

通信エラー時に表示する TextView, Button は、constraintlayout.widget.Group によってグループ化し、表示・非表示を切り替えるように変更しました。

最終的に ConstraintLayout を1つにまとめたところで改善に至りました。結果から原因の推察になりますが、ConstraintLayout を複数並べることがパフォーマンスに影響を及ぼしていたようです。

この件を調べてみると、いくつか同様の記事が見つかりました。 (もっと早く出会いたかった。。)

既存アプリのレイアウトをConstraintLayoutに書き換えた - ビー鉄のブログ

ConstraintLayoutをネストすると激重になる - Qiita

ConstraintLayout を扱う際は、入れ子や複数の羅列は避けてレイアウトを組むように注意すべきです。

なぜ気づけなかったのか?

実装時に気づけなかったことについて振り返ってみました。

まず起きているカクつき自体が、エミュレータや手元の実機ではわかりにくく環境依存の点があったと思います。実際に発覚したのはstageビルドで他の検証機を使った段階でしたので、どこの修正で影響を及ぼしたのかが追いにくい状況になっていました。

また、経験的にも多少のネストのネストでそこまでパフォーマンスに影響があるケースがなかったので、すぐにレイアウトの問題という発想には至りませんでした。一般的にパフォーマンス低下の要因には、レイアウト以外にも考えられ、複数の通信をする場合に同期的な実装になっていることやデータの処理側の可能性もあり、検討をつけにくくなっていました。

パフォーマンスへの影響には様々な要因が考えられ、問題の解決に時間がかかってしまいました。

レンダリングパフォーマンスを意識した実装

Androidレンダリングパフォーマンスを考えるにあたって、View の階層化は気をつけなければいけない問題の一つです。

これはAndroid の公式ドキュメントでも言及されており、開発者は注意しながらレイアウトの構築をする必要があります。

developer.android.com

レイアウトに特に時間がかかる最も一般的なケースは、View オブジェクトの階層が互いにネストされている場合です。ネストされた各レイアウト オブジェクトがレイアウト ステージにコストを追加します。階層がフラットであればあるほど、レイアウト ステージが完了するまでにかかる時間が短くなります。

公式ドキュメントではレイアウトのネストについて上記のように説明されており、ConstraintLayout に限らず、ネストを避けたレイアウトを組むことが重要です。

ConstraintLayout はネストを回避したレイアウト構築が可能なコンポーネントであり、View に制約を付与することで位置関係を定義します。

今回のケースでは ConstraintLayout を使いつつも、リストとエラー表示系を別々のレイアウトに配置していたことが原因でした。実際には単一の ConstraintLayout で実現でき、誤った使い方が招いた問題でした。

developer.android.com

GPUプロファイルを見てみる

レンダリングのパフォーマンスに問題があるときに、原因の究明を助ける方法が公式ドキュメントで紹介されています。

developer.android.com

今回はGPUプロファイルを使うことで、ヒントを得られました。

developer.android.com

設定方法を紹介します。

Android 端末の開発向けオプションを開いて 「HWUI レンダリングのプロファイル作成」から 「バーとして画面に表示」をクリックします。

HWUI レンダリングのプロファイルの作成
開発者向けオプションの「HWUI レンダリングのプロファイルの作成」の設定

画面上に色付きのグラフが表示されます。 ここでは、改善前のサンプルアプリでGPUプロファイルを見てみます。

GPUプロファイルを使った色付きグラフ
GPUプロファイルを使った色付きグラフ

色の意味は公式ドキュメントで説明されており、それを足がかりに原因を考えてみました。

developer.android.com

図では右側の黄緑と深緑部分が長くなっており、処理に時間を要していることが分かります。

黄緑は「Measurement / Layout(測定 / レイアウト)」を意味しており、ここが長い場合は、レイアウトに原因があることが考えられます。今回のケースでは、これをきっかけにレイアウトの見直しができました。

深緑は「その他」を意味しており、メインスレッドで重い処理が実行されている可能性が考えられます。こちらもリスト面のデータ通信が悪いのかなどの検討に繋がりました。

その他のパフォーマンス調査の方法

公式ドキュメントでは、GPUプロファイル以外にもパフォーマンスの調査方法が紹介されていますが、いくつか試してみてGPUプロファイルが手軽に試せたのが良かったです。

その他の方法についての所感です。

  • Systrace
    • 詳細な情報が確認できる反面、その情報がどういうものなのかを調べる必要があり、扱うのが難しく断念しました。。
  • lint
    • 冗長な実装がある場合にワーニングを表示してくれます。今回のケースではヒントとなる情報は得られませんでしたが、通常の開発時に一度使って無駄がないかを調べるのに有用と感じました。
  • Layout Inspector
    • 視覚的にレイアウトの階層を表示できて、ボトルネックの調査と言うよりは、レイアウト構築時にXMLやプレビューだけではわかりにくい階層を確認する場合には有用に感じました。

まとめ

Android の ConstraintLayout を扱う際の注意点とレンダリングパフォーマンス問題の解決の緒について説明しました。 Layout のネストの注意点は基本的ではありますが、私の開発経験では意識しなくとも問題になるケースがなかったので、解決にたどり着くまでに時間を要しました。 プロダクト開発では段階によって問題が複雑化し、問題の切り分けが難しくなってきます。 そういった問題に対して、GPU プロファイルを確認して原因を調査していくことで、改善につなげられました。

お手軽AWS情報収集のススメ

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

皆さんは、AWSの新情報の収集をしていますか? 必要だとは思いつつも、なかなか出来ていないという方も多いかと思います。 今回は、AWSの新情報をお手軽に手に入れる方法を紹介します。

AWSの新情報

AWSでは、毎日のように様々な新機能が発表されています。 その中には、大きいものから小さいもの、自分に関係するものから現状だとしないものまでありますが、すべての新情報を毎日チェックするのは大変です。 また、日本人など英語圏でない方にとっては、情報によっては最初は英語のみで発表される場合があることもあり、追いにくい一因にもなっているかと思います。 つまり逆に言えば、

  • 様々な新情報の中から特に大きめの話題をピックアップしてくれて
  • 日本語でまとめてくれている

ものがあれば新情報は追いやすくなるのではないでしょうか?

実は、そんな都合の良いブログがあります。

週間AWS

週間AWS は、まさしく上記の条件を満たしてくれるブログです。

f:id:excite-takayuki-miura:20210419134201p:plain
週間AWS

  • AWS公式で
  • 毎週、その週のAWS新情報の中からピックアップした情報を
  • 日本語で、簡単にまとめてくれている

ブログとなっています。

多くの場合、月曜日に先週一週間のトピックをまとめて更新してくれているので、週明けに見るという習慣をつけやすいかと思います。

まとめ

AWSでは、日々新しい機能が公開されています。 面倒な運用作業をマネージドなサービスとして代替してくれるようなものもあれば、適応すればコスト削減になるものもあり、可能な限り早く情報を手に入れることで様々な恩恵を受けることが出来ます。 また、週間AWSの情報を更に自身が携わるサービスに関係するものに絞り込んで、チームメンバーにシェアするのも良いかもしれません。

ぜひ週間AWSを活用していきましょう。

ReflectionTestUtilsで変数に値をセットする

エキサイト株式会社の中です。

Spring Bootでpropertyの値を取得する時、@Valueで取得すると思います。 @Valueは外部から値を注入しているので、UTをする時mockを設定しなければなりません。

ReflectionTestUtilsを使って設定する方法を説明します。

@Service
public class TestServiceImpl implements TestService {
    @Value("${spring.test.hoge}")
    private String hoge;

    @Override
    public String test() {
        return hoge;
    }
}

で取得できると思います。

ユースケース

  • @Valueでpropertyの値を取得している

入力例

テストファイルです。

class TestServiceImplTest {
    @InjectMocks
    private TestServiceImpl testService;

    @Test
    void test() {
        ReflectionTestUtils.setField(testService, "hoge", "naka", String.class);

        Assertions.assertEquals("naka", testService.test());
    }
}

出力例

テスト結果のコンソールです。

Task execution finished ':module:service:test --tests "com.service.TestServiceImplTest.test"'.

解説

ReflectionTestUtils.setField(testService, "hoge", "naka", String.class);
  • プロパティファイル名を埋め込みたいmockのtestService
  • プロパティファイル名を埋め込む変数名hoge
  • プロパティファイル名を埋め込む値naka
  • 埋め込む型String.class

簡単に埋め込むことができました。

MockHttpServletRequestでcontrollerのテストする(1)

エキサイト株式会社の中です。

Java Spring Bootでcontrollerのテストをする方法を説明します。

ユースケース

  1. controllerのテストをする。
  2. Cookieを設定する

題材

コード例

@GetMapping("cookie")
public String showCookies(
        @CookieValue(name = "naka", required = false) Cookie cookieNaka
) {
    return Optional
            .ofNullable(cookieNaka)
            .map(cookie -> cookie.getValue())
            .orElseThrow(BadRequestException::new);
}

入力例

実際のTestクラス。

@ExtendWith(MockitoExtension.class)
class TestControllerTest {
    @InjectMocks
    private TestController testController;

    private MockMvc mockMvc;

    @BeforeEach
    public void before() {
        this.mockMvc = MockMvcBuilders.standaloneSetup(this.testController).build();
    }

    @Test
    public void getShowCookies() {
        try {
            MockHttpServletRequestBuilder getRequest = MockMvcRequestBuilders.get("/cookie");
            final String contentAsString = this.mockMvc.perform(getRequest.with(request -> {
                Cookie[] cookies = new Cookie[] {
                        new Cookie("hoge", "hoge"),
                        new Cookie("abe", "abe"),
                        new Cookie("naka", "sho")
                };
                request.setCookies(cookies);
                request.setParameter("name", "");

                return request;
            }))
                    .andDo(MockMvcResultHandlers.print())
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    .andReturn().getResponse().getContentAsString();

            assertEquals("sho", contentAsString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

出力例

テスト結果のコンソール。

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /cookie
       Parameters = {name=[hogehoge]}
          Headers = [Cookie:"hoge=hoge; abe=abe; naka=sho"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.controller.TestController
           Method = com.controller.TestController#showCookies(Cookie)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=ISO-8859-1", Content-Length:"3"]
     Content type = text/plain;charset=ISO-8859-1
             Body = sho
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
BUILD SUCCESSFUL in 2s
13 actionable tasks: 1 executed, 12 up-to-date
23:25:25: Task execution finished ':api:test --tests "com.controller.TestControllerTest.getShowCookies"'.

条件

  • Cookieに設定する

解説

@BeforeEach
public void before() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(this.testController).build();
}

apiの受け口をmockで用意します。

MockHttpServletRequestBuilder getRequest = MockMvcRequestBuilders.get("/cookie");

受け口のリクエストのmockを作成します。

final String contentAsString = this.mockMvc.perform(getRequest.with(request -> {
    Cookie[] cookies = new Cookie[] {
            new Cookie("hoge", "hoge"),
            new Cookie("abe", "abe"),
            new Cookie("naka", "sho")
    };
    request.setCookies(cookies);
    request.setParameter("name", "hogehoge");

    return request;
}))

受け口の細かい設定を追加します。 クッキーやパラメータを設定することができます。

.andDo(MockMvcResultHandlers.print())

リクエストの詳細をコンソールに出力します。

.andExpect(MockMvcResultMatchers.status().isOk())

レスポンスステータス200であることをテストします。

.andReturn().getResponse().getContentAsString();

レスポンスを文字列で受け取ります。

実際のテストについて

controllerのテストは上記をみていただけたら分かる通り、かなり設定が複雑で大変です。

プロジェクトの規模によりますがUTは行わず、ITa以降からのテストで十分確認が取れると思いますので、参考程度にみていただけると幸いです。

Javaで戻り値を多値で返したい

エキサイト株式会社メディア開発の佐々木です。

Javaは、戻り値を1つしか返せない言語です。GoやPythonは複数返すことができますが、言語的にサポートされていないので、ライブラリを使って代用します。

設定

使うライブラリは、有名なcommons-lang3になります。Gradleで依存解決を行います。

dependencies {
    ...
    implements "org.apache.commons:commons-lang3:3.10"   // 追加
    ...
}

戻り値2つ

戻り値2つには、Pairをしようします。

public Pair<Integer, String> getPair(){
      Integer id = 1;
      String name = "hogehoge";
      return Pair.of(id,name);
}

// 使用側
Pair<Integer, String> pair = getPair();
pair.getLeft();   // 左側取り出し
pair.getRight();  // 右側取り出し

Pair.of(L,R) を使用して、Pairオブジェクトを作り、返却します。利用する方は、左にセットされた値と右にセットされた値をそれぞれ取り出すメソッドが実装されているので、すぐに取り出せます。

戻り値3つ

戻り値3つには、Triple を使用します。

public Triple<Integer, Boolean, String> getTriple(){
      Integer id = 1;
      Boolean isActive = true;
      String name = "hogehoge";
      return Triple.of(id,isActive,name);
}

// 使用側
Triple<Integer, Boolean, String> triple = getTriple();
triple.getLeft();  //左側取り出し
triple.getMiddle(); //真ん中取り出し
triple.getRight(); //右側取り出し

Triple.of<L,M,R>を使用して、Tripleオブジェクトを作り、返却します。利用する方は、それぞれの値を取り出すようのメソッドが実装されているので、すぐに取り出せます。

さいごに

Pairは、Map.Entryを拡張していて、Tripleは、独自実装をしています。(PairはgetKey(), getValue()のメソッドがあります)GolangやPythonのように、変数に一気入れられませんが、事は足りますので必要に迫られれば使っていきたいと思います。

エキサイト株式会社では、長期インターン等を募集しております。まだHP等ができていませんが、wantedlyから連絡いただければ対応させていただきたいと思います。

www.wantedly.com

JavaのOptional型の利用方針

エキサイト株式会社メディア開発の佐々木です。

メンバーもJavaの開発に慣れてきて、今まで触ってきた言語にはない機能だったり仕組みだったりに格闘している日々です。今日は、Optionalについてです。

Optionalとは

Javaは、nullを許容しているレガシー言語です(笑)Optional型は、値がnullかどうかわからないものをくるんでくれる型になります。nullにオブジェクトとしてアクセスしてしまうとNullPointerExceptionが発生していますので、nullかどうかわからないものには有用です。Optionalにはいくつかのメソッドが実装されています。

データ取得系

Optionalには、取得系と処理系のメソッドが実装されています。

  • get()

値を取得します。nullの場合に例外を投げます。

Optional<String> optional = Optional.of("sample");
String s = optional.get();     // Nullだと NoSuchElementExceptionを投げます
  • orElse()

値を取得して、nullの場合に返す値も設定します。orElseの中は、nullじゃなくても実行されます。

Optional<String> optional = Optional.of("sample");
String s = optional.orElse("other");     // 必ずorElseが実行されてotherが設定されて、nullだったら設定された値が返る
  • orElseGet()

値を取得して、nullの場合に返す値も設定します。上記との違いは、nullだったときに、ラムダ式が実行されている点です。nullのときに返却する処理が重い場合は、こちらを選択する方がいいでしょう。

Optional<String> optional = Optional.of("sample");
String s = optional.orElseGet(() -> "other"); // nullだったときだけ、ラムダ式が実行され"other"が返る
  • orElseThrow

値を取得できなかったら例外を設定してthrowします。

Optional<String> optional = Optional.of("sample");
optional.orElseThrow(() -> new RuntimeException("no value"));

処理系

こちらは戻り値がない処理系になります。

  • ifPresent

値があるときだけ、処理するメソッドになります。

Optional<String> optional = Optional.of("sample");
optional.ifPresent(e -> System.out.println(e));
  • ifPresentOrElse

値があるときとなかったときの処理をそれぞれ書く必要があります。

Optional<String> optional = Optional.of("sample");
optional.ifPresentOrElse(e -> System.out.println(e), () -> System.out.println("no value"));

利用箇所

Optionalは通常の型ですので、どこでも使えます。が、使う場所は選んだほうが良いと思います。

  • メソッドの戻り値
    public Optional<String> getOptionalName(){
       // 処理
    }

ここはメソッドでOptionalが返却されるということがわかっています。なんで、Optionalが返却されるかというと、このメソッドでは、 orElse() の判断がつかないからということになります。メソッドの戻り値にOptionalを使うのは有用かなと思います。

  • メソッドの引数

Javaにはオーバーロードがあるので、それで呼び分けで良いとおもいます。わざわざOptionalの生成をおこなったりするのも面倒です。

    public Optional<String> getOptionalName(Optional<String> op){
       // 処理
    }
  • インスタンスフィールド

Optionalは、Serializableではないのでインスタンスフィールドには使用しない方がよさそうです。

class Data {
    
    ...
    Optional<String> optionalName;   // やってないけない
    ...

}
  • ローカル変数

わざわざOptionalを生成するのは手間が無駄だとおもいます。大きいメソッドの場合はあるかもですが、そもそも大きいメソッドを作るべきではありません。

おわりに

OptionalはNull許容なレガシー言語であるJavaにとっては、かなり有用な型になりますが、使いどころのメンバーと認識合わせはしておいた方がよさそうです。どこでも使えますけど、有効な使用箇所はメソッドの戻り値くらいかなと思います。

【Flutter】DatePickerの色を変更する

エキサイト株式会社のメディア事業部でモバイルアプリ開発をおこなっている、辻です。

日時や時刻の選択を行うDatePickerは、モバイルアプリやWebアプリ等、フロントエンドでは使用することの多いUIコンポーネントだと思います。 今回はそんなDatePickerをFlutterで利用する際の、色の変更方法について書いていきます。

DatePickerの使用

FlutterでDatePickerを使用するためには、showDatePicker()を、DatePickerを表示したいタイミングで呼び出します。

final selectedDate = await showDatePicker(
    context: context,
    // DatePicker表示時に出す日付
    initialDate: DateTime.now(),
    // 選択できる一番古い日付
    firstDate: DateTime.utc(2000),
    // 選択できる一番新しい日付
    lastDate: DateTime.now()
);

f:id:excite_yuchiro22:20210414194013p:plain
DatePicker表示

色の変更

DatePickerの色を変更するには、builderプロパティにThemeを適用します。

final selectedDate = await showDatePicker(
    context: context,
    initialDate: DateTime.now(),
    firstDate: DateTime.utc(2000),
    lastDate: DateTime.now(),

    // 追加
    builder: (BuildContext context, Widget child) {
        return Theme(
            data: ThemeData.light().copyWith(
                colorScheme: ColorScheme.light().copyWith(
                    // 変更したい色
                    primary: Colors.red,
                ),
             ),
             child: child,
        );
    }
);

f:id:excite_yuchiro22:20210414194102p:plain
DatePicker色変更

※ 2020年に行われたFlutterのTheme Systemのアップデートに伴い、Theme.dataの設定方法が変更されています。

Before

Theme(
    data: ThemeData.light().copyWith(
        primaryColor: Colors.red, 
     ),
     child: child,
);

After

Theme(
    data: ThemeData.light().copyWith(
        colorScheme: ColorScheme.light().copyWith(
            primary: Colors.red,
        ),
    ),
    child: child,
);

参考

DatePicker

DatePicker custom theme is not working after the latest update

Theme System Updates

おわりに

徐々に気温も暖かくなり、私は早くバイクに乗りたい気分です。

さて、エキサイトでは、一緒に働けるモバイルアプリエンジニアを募集しています! もし興味がありましたら是非こちらのリンクから、お話しましょう! www.wantedly.com

【Stream API】groupingBy と mapping でリストをマッピングしつつグルーピングする

エキサイト株式会社の西牧です。

groupingBy を使うとリストをグルーピングできますが、mapping もあわせて使うことでリストに何らかの処理をしつつグルーピングできることを新たに知ったので、その方法を紹介します。

想定ケース

以下のようなリストがあるとします。

@Value
class Row {
    long id;
    String name;
    String hobby;
}

Row row1 = new Row(1, "sato", "music");
Row row2 = new Row(1, "sato", "baseball");
Row row3 = new Row(2, "suzuki", "programming");
Row row4 = new Row(2, "suzuki", "game");
List<Row> rows = List.of(row1, row2, row3, row4);

このリストを UserDetail というクラスのリストに変換する、つまり、User ごとの Hobby リストを作るというのがやりたい処理です。

UserDetail(user=User(id=1, name=sato), hobby=[Hobby(value=music), Hobby(value=baseball)])
UserDetail(user=User(id=2, name=suzuki), hobby=[Hobby(value=programming), Hobby(value=game)])
@Value
class User {
    long id;
    String name;
}

@Value
class Hobby {
    String value;
}

@Value
class UserDetail {
    User user;
    List<Hobby> hobbies;
}

mapping を使わないコード

リストを任意のキーでグルーピングするのは、Stream API の collectgroupingBy を使うことでできます。

今回は User ごとにデータをグルーピングしたいので、User インスタンスをキーとしました。

groupingBy の第一引数がグルーピングした結果のキーです。

第二引数の LinkedHashMap::new はグルーピング後のリストの順序を保つためのものです。

第三引数でリストごとにグルーピングすることを指定しています。

本当は Hobby インスタンスのリストを作った上で、それらを User インスタンス でグルーピングしたかったのですが、その 2 つを同時に行う方法がわからず、こういう形になってしまいました。

LinkedHashMap<User, List<Row>> map = rows
        .stream()
        .collect(Collectors.groupingBy(e ->
                new User(e.getId(), e.getName()
                ), LinkedHashMap::new, Collectors.toList()));

List<UserDetail> userDetails = map.keySet()
        .stream()
        .map(e -> {
            List<Row> tmpRows = map.get(e);
            List<Hobby> hobbies = tmpRows
                    .stream()
                    .map(r -> new Hobby(r.getHobby()))
                    .collect(Collectors.toList());
            return new UserDetail(e, hobbies);
        }).collect(Collectors.toList());

mapping を使ったコード

後ほど上司の方に質問して、groupingBy の第三引数で Collectors.mapping を使うと、 「Hobby リストを作った上で、それらを User でグルーピングする」ことができるとわかり、修正したものが以下です。

Collectors.mapping(e -> new Hobby(e.getHobby()), Collectors.toList()) の部分で Hobby のリストを作っています。

LinkedHashMap<User, List<Hobby>> map = rows
        .stream()
        .collect(Collectors.groupingBy(e -> new User(e.getId(), e.getName()),
             LinkedHashMap::new,
                Collectors.mapping(e -> new Hobby(e.getHobby()), Collectors.toList())
        ));

List<UserDetail> userDetails = map.keySet()
        .stream()
        .map(e -> new UserDetail(e, map.get(e)))
        .collect(Collectors.toList());

おわりに

PHP の連想配列に慣れた身からすると、Java はリストの詰替えがやりづらいなと思っていたのですが、Stream API を使えば簡単にできました。

Stream API はまだ使いこなせていない部分もあるので色々試していきたいです。

AWS ECSにおけるログ保存コスト削減のススメ

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

クラウド化・コンテナ化が普及してきた昨今、AWS ECSでWebアプリケーションを構築する場面が増えてきているのではないでしょうか。 また、Webアプリケーションを構築する際にアクセスログやエラーログを保存しておくことは、サービスの運営のためにほぼ必須と言っても良いでしょう。

ECSでアプリケーションを構築する場合、デフォルトではCloudWatchにログを流すことになりますが、CloudWatchへのログの保存は思いの外コストが掛かってしまうものです。

そこでここでは、ログ保存のコスト削減のためにどんな方法があるのか、そしてそれぞれにどんなメリット・デメリットがあるのかを紹介していきます。

ログ保存の流れ

ログの保存は、

  1. コンテナからログを送信する
  2. ログを保存しておく

という流れになっています。 この内、

2. ログを保存しておく

については、特別な理由がなければS3に保存するということで構わないと思います。 CloudWatchに比べてS3のほうが保存コストが安く、また Amazon Athena を利用することで、SQLクエリを使ってファイルの中身を閲覧することが可能です。

CloudWatchにもデフォルトで「S3にログをエクスポートする」機能が存在するので、Lambdaの定期実行等で一定時間以上経ったログをS3にエクスポートすることができます。

今回は、

1. コンテナからログを送信する

に焦点を当てていきます。

ログ流し先を変更する方法

ECSではデフォルトでCloudWatchにログを流してくれますが、CloudWatch以外にログを流すためには一工夫必要です。

Fluent Bit をログルータとして利用することで、Fluent Bitで提供している向き先にログを流すことができるようになります。

大量のログでも大してCPUやメモリを使うことはないので、気にせず使ってよいのではないでしょうか。

ログ流し先3選

ここからは実際に私が試したログ流し先3つと、それぞれのメリット・デメリット紹介します。

CloudWatch

CloudWatchを使用するのも、必ずしも悪いわけではありません。

メリット

  • 欠損なくログを流すことができる
  • 準備がほとんど必要ない

デメリット

  • コストが掛かる
  • S3にログをエクスポートする場合、ひと手間掛かる

S3

Fluent Bitを使えば、CloudWatchを使用せず直接S3にログを流すことが可能です。

メリット

  • コストが非常に安い

デメリット

  • EFSなどの共通ストレージ等を使用しないと、コンテナの破棄のタイミングによってはログが欠損する可能性がある

Firehose -> S3

Fluent Bitから Amazon Kinesis Data Firehose にログを流し、それを今度はFirehoseからS3に流す方法です。

メリット

  • CloudWatchを使う方法に比べてコストが安い
  • 欠損なくログを流すことができる

デメリット

  • あらかじめFirehoseの準備をしておく必要がある
  • ログが大量にある場合、デフォルトのFirehoseのスペックでは不足する場合があり、その場合はAWS側に申請してスペックを上げて貰う必要がある
  • S3に直接流す方法に比べてコストがかかる

まとめ

以上から、ユースケースとして、

- ログの欠損が絶対に起きてはならない場合
  -> CloudWatchかFirehose

- 多少ならログが欠損しても良い場合
  -> S3

を選ぶのが良いかと思います。

また、今回はこの3つのみを紹介しましたが、サードパーティのアプリケーションを使用するなどログの保存方法は他にも存在します。 場合によってはそちらを検討してみても良いでしょう。

ログの保存は必須であるからこそ、可能な限りコスパよく保存していきたいものです。 特にサービスへのアクセスが増え、ログのコストが気になりだした方は、一度検証してみると良いのではないでしょうか。

Javaでメソッド参照を使うかラムダ式を使うかの判断

エキサイト株式会社 メディア開発佐々木です。

現在Spring/Javaで開発するにあたって、関数型インターフェースを呼ぶときにラムダ式でもメソッド参照でも書ける場合があります。どちらを使った方が、継続的な開発にいいのかを簡単にですがまとめてみます。

引数なしの場合

関数型インターフェースでArrayListのインスタンスを返却するだけの宣言ですが、下記の2パターンがあります。

// ラムダ式
Supplier<ArrayList<Integer>> supplierList = () -> new ArrayList<>();


// メソッド参照
Supplier<ArrayList<Integer>> supplierList = ArrayList::new;

パッと見た読みやすさでは、メソッド参照の方がカッコいいし良い選択かもしれません。 ArrayList::new と書いてあるだけなので、 new ArrayList() が入ってるんだろうなと容易に想像ができます。一方、ラムダ式では () -> のような引数がないことを明示しなくてはなりません。コードを書く側からすると面倒ですが、読む側からすると、引数がないことを明示してくれていた方が理解しやすいと思います。

引数が複数の場合

引数が複数の場合は、下記のような実装になります。

// ラムダ式
BinaryOperator<Integer> sum = (a , b) -> Integer.sum(a,b);

// メソッド参照
BinaryOperator<Integer> sum = Integer::sum;

メソッド参照では、計算されていることはわかりますが、引数に何が指定されているかはパッと見はわかりません。 あとから使う人がBinaryOperatorを知っていれば引数を2つとることはわかりますが、知らない人は調べる必要があります。片やラムダ式は、引数を2つとることが明示されています。こちらは、BinaryOperatorを知っている必要がありません。

まとめ

EffectiveJavaにはメソッド参照を使った方がいいとありますが、個人的には読む人がわかりやすく汎用的なものを選びたいと思います。メソッド参照はすっきりしていてカッコイイよく、IDEからも変換を促されるかもしれませんが、読みにくかったり等するのであれば、チーム開発という上では使わないという選択もありかなと思います。IDEのアシストをそっとオフにしたいと思います。

下記、参考にさせていただきました。 nowokay.hatenablog.com