エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

【極論ですけど】JavaでforEachを使ったら負けだと思う

正直使い所がわからないんですよね、forEach.
ログとかprintfとかする以外は…

コレクションを更新するな

未だに時々見るのがこういうコード。

List<X> list = new ArrayList<>();
適当なコレクション.forEach()
  .map(t -> list.add(t.getXXXX()));

これとっても良くない。

スレッドセーフでない

上記をあまり深く考えずにパラレルストリームにしちゃいますと

List<X> list = new ArrayList<>();
適当なコレクション.forEach().parallelStream()
  .map(x -> list.add(x.getXXXX()));

ArrayListに対する追加処理がスレッドセーフでないため、確率的に不具合が起きてしまいます。*1

パラレルにしなければいい…のですが、Project Lambdaの目的の一つがマルチコアへの対応なわけで、「parallelStreamを使って並列化できないものはラムダ式で書くな」くらいに私は思っています。(私の勝手ルールではありますが)

Lambdaで更新するの「関数」でない

Lambda式内では外で定義した変数に代入ができません。
これは、「状態」を極力排除することによって、バグの入り込む余地を減らすため… と私は認識しています。
ですが、上記のようにコレクションに対する操作や、Setterでの代入はJavaの言語仕様上できてしまいます。

…とはいえ、できるからやっていい、というものでもないと思います。

「リストを作る」と明確に書く方法がある

List<X> list = 適当なコレクション.stream() //parallelも可
  .map(X::getXXXX)
  .collect(Collector.toList());

そもそもこのように、リストを作るための書き方が用意されているのに、これを使わずに汎用的なforEachでなんとかする… というのは美しさに欠けます。

更新しないとなると、forEachは出力くらいしか使いみちがないのでは

上で書いたとおり、コンパイルエラーにはならない… とはいえ、Lambda式内で何かを「更新」するのは不適切と考えます。
そうなると、冒頭に書いたとおり、何かの出力くらいしか使いみちがないなぁ… ということになり、
よっぽどのことがない限り、「forEachを使ったら負け」だと思うのです。

*1:私が経験したことのある事象だと、「適当なコレクション」よりも多い要素がlistに追加されるという謎現象が発生し、その要素をgetするとnullが入ってる → ヌルポで落ちる というものを何度かみました。

"Rename Hackathon"をしてみました

チーム内で、お祭り的なイベントとして、"Rename Hackathon"というイベントをチーム内でしてみました。

ルール

  • 参加者は30分間ひたすらプルリクエストを送り続ける
  • 「リネーム」のみ許可。名前を変更する対象は、クラス、メソッド、変数名、パッケージ名 など何でも良い
  • 1つのプルリクエストは、1単語だけ直して良い。複数の単語を修正したプルリクエストはルール違反として却下*1
  • 30分後の「リネームタイム」の後に通常のコードレビューを行い、Approveされたものだけがマージされる
  • 同じ箇所を複数人が直した場合は投票でどちらを選ぶか決める
  • 最もマージされた数が多かった人は表彰

結果

  • 参加者 : 6人(因みに…4名は日本オフィス。2名はインドオフィス。リネームタイムはリモートで同時に。)
  • 30分間に出された総プルリクエスト数 : 32
    • ルール違反 : 2件(複数箇所修正、パッケージを1回層削除)
    • 重複したため投票で却下 : 2件
    • コードレビューで却下 : 8件(まだ一部レビュー中なので増えるかも)
    • ということで残ったプルリクエスト数 : 12 ~ 20
  • 修正の例
    • クラス
      • 不要(しかも意味不明)な接頭辞や接尾辞の削除
      • 省略されすぎていて分かりにくい名前を展開
    • メソッド
      • booleanを返すcheckXXXX メソッドを isXXXXX メソッドに
    • 変数
      • i などの無意味な名前を 意味のある名前に

所感

楽しかった

お祭りみたいで楽しかったです。ある程度ゲーム性のあるルール*2をしっかり作ったので、数を競うのも楽しんでくれた感がありました。

コードがきれいになった

これはもちろん。本来なら最初のコーディング時に埋め込まれるべきでない名前もちらほらありましたが、どうしてもできてしまうのは避けられないもの。
また、開発者のドメインナレッジが増すことによってより良い名前を後から思いつくこともあります。
そういった、普段のもやもやを片付けられた良い機会でした。

初級者の学びに

32件中8件はコードレビューで却下ということで、たかだかRenameなんですが、意外と他の人の了承を得られていないんですね。*3
「良い名付けとはなにか」の学びができた、良い機会になったと思います。

*1:プルリクエスト単位でマージの可否を判断するため

*2:本来の目的は安全にリファクタリングをするため

*3:レビューは参加者同士の相互レビューです

「解決策から考える病」に名前をつけたい

…って、タイトルで名前つけちゃってますが。

どうしても人間、「なんとなく思いついたソリューション」に飛びついちゃうんですよね。

寝坊しまくってるから目覚まし時計を買わなきゃ…! ってなりがち。
f:id:taichiw:20190206235438p:plain

その前に、そもそもなぜ寝坊しているか? について考えないといけない。*1
f:id:taichiw:20190206235455p:plain

※上の画像は以前に書いたこちらの資料より。もともとKPTのやり方を説明した資料ですが、根本的な考え方は共通だと思います。

自分がこの考え方ができるのは… 弊社の入社前課題でもらったこの本のおかげっぽいです。
他にも色んな経験が元になっているのだとは思いますが、「なんで自分、こういう考え方をするようになったんだっけ?」と思案したときに、思い出すのはこの本です。

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

世界一やさしい問題解決の授業 [ 渡辺健介 ]
価格:1296円(税込、送料無料) (2019/2/6時点)


適当にググってたらこんなブログも見つけました。(Baseball Studyで知ってる方だ…!) nihonbuson.hatenadiary.jp

*1:実際のチームだと…「そもそも寝坊は悪いことなのか? 悪いとしたらなぜ?」まで一旦掘り下げたいところです

JavaのHashMapは無限ループを引き起こす

知らんかった。理由がぼんやりはイメージ湧くけどしっくり来てないので後でちゃんと調べ…たい。(だいたいやらないパターン…)

d.hatena.ne.jp
www.atmarkit.co.jp
wadahiro.hatenablog.com


回避方法はいろいろあるみたいだけど、ConcurrentHashMapをつかってスレッドセーフにするのが楽なのかな?
web.plus-idea.net

価値観ババ抜き大体験会!に参加してきました #devlove

悩めるオジサンの私が、自分を見つけるためにぴったりそうな価値観ババ抜き体験会に参加してきました。
https://devlove.doorkeeper.jp/events/85438

価値観ババ抜きって何?? というのを書こうと思ったのですが 冗長になる&講師じゃない自分が変に説明して誤解が広がっても嫌だな
ということで、上記のイベント告知 及び、コチラの公式サイトのリンクをご参照ください。
http://myvaluecard.com/

いきなり考えさせられる名札書き

f:id:taichiw:20190121225328p:plain
何この「どこから来た」って!
ストレートに社名を書こうとも思ったんですが、あぁ、なんかあるんだろうなこれ と 邪推し、こんな感じに。
f:id:taichiw:20190121225438p:plain
「『シングルプレーヤーとマネジメントの間』から来ました」って… 今見返すとすごく恥ずかしい。
でもこの手の、自分と対話するゲームするときはこのくらい、のっけから自分を晒していかないとですよね!

ちなみに、この名札は、開始前の雑談以降は全く使いませんでした(笑)

ババ抜きの結果

さて、最終的に5枚のカードが手元に残るのですが… 私の結果はこちら!!
f:id:taichiw:20190121225716p:plain

いやいやいやいや 「美」ってなんだよ、「美」って。よりによって俺が「美」って。
って思うんですが。

このカード、偶然にも最初の5枚に入ってまして、

「なんだこれ? すぐ捨てよう」

と思ったものの、

「でも、バグのないシステム、きちんと『切られ』た、読みやすいコードって、まさに『美』だよなぁ」

と思ったら 

「これはこれで 大切かぁ…」

と思い

気づけば最後まで残ってしまいました。

また、ゲーム中は各カードはあまり繋がりがなく、バラバラに5枚残った… と感じていたのですが、
よくよく見ていると…

なんとなーく 隣のカード同士の関連のようなものが見えてきました。
f:id:taichiw:20190121230148p:plain

まさかの、最後の1枚、「メイン」になったのは…

更にここから追加のワーク。残った5枚の中から1枚のメインと2枚のサブを選ぶことに。

「そら、この中からメインを選ぶんだったら『楽しさ』よ」

と思ったんですが、どうしてもしっくりこない。

どうしてもどうしても気持ちがそっちに行かず…

最終的に私が選んだのは… これ。

f:id:taichiw:20190121230842p:plain

「『美』しいシステムを作るには『知識』が必要であり、それを学ぶモチベーションとして支えるのが『楽しさ』」

という、「美」を頂点にしたピラミッドができてしまいました。


えええ…

これだと俺の中で一番大事なことが「美しい」ことになってしまうんですけど……

とはいえ、恥ずかしさとか、どう思われるかとか、そういうのを捨てて、自分に問いかけたときに、

上の3枚の三角が最も最終結果として「美しい」 と思うくらい「美」を意識してしまったんだからしゃーない。

もう一回やったらどうなるんだろう

今回は偶然にもはじめの手札に「美」があったのでこのような結果になってしまったのですが、もし「美」がずっと他の人に持たれていたり、あるいはずっと場にあったとしたら*1、全く違う結果になったのでしょうか。
その場合自分は何をメインに置いたのか…

時期をおいてまたやってみたいものです。

実際、平日の夜にやるのか休日にやるのかでも結果が変わるとのこと。
脳内がお仕事モードなのかプライベートモードなのか、あるいは、仕事モードの中でも何に時間や脳みそを取られているのか といった状況によってかなり結果が変わりそうですね。

最後にお手紙タイム

最後は、一緒にババ抜きをした他の3名に向けて、お手紙を書いて渡す というちょっと恥ずかしいけど嬉しい時間でした。
f:id:taichiw:20190121231527p:plain

*1:カードを取られるたびに自分で場から一枚好きなものを選ぶことができるのですが… 自分から「美」は取らないような気がします。それとも今回みたいに、たまたま目があってしまった後に同じ思考になって結局取るんだろうか…

あらゆる「システム」の挙動は入力と出力だけで説明できる

どんな粒度、どんな用途の「システム」も、挙動は入力と出力だけで説明できます。
f:id:taichiw:20190118235359p:plain

プログラムのサブルーチン(function, method, ...)

「引数」と「返り値」で挙動が説明できます。

public String greet (String name){
    return "Hello " + name + "!";
}

Microserviceアーキテクチャの、各Service

APIのリクエストとレスポンスで挙動が説明できます。
f:id:taichiw:20190119000332p:plain

Microserviceアーキテクチャ全体

内部すべてをブラックボックスと見て、一番外側のAPIのリクエストとレスポンスで挙動が説明できます。
f:id:taichiw:20190119000823p:plain
f:id:taichiw:20190119000848p:plain

Webページ

リクエストであるURL(+POSTで送られるテキストや画像などのリクエストボディ)と、返却されるHTMLやその他コンテンツで挙動が説明できます。
f:id:taichiw:20190119001413p:plain

でも、データ更新したりするでしょ? 入力と出力だけじゃ説明できないじゃない!

このようにシステムを見ると、「状態が更新される」ことも挙動の説明としてする必要があります。
f:id:taichiw:20190119002052p:plain

ですが、ここだけをシステムとして見ると… DBへの更新は「出力」の一種と見ることができます。
f:id:taichiw:20190119002118p:plain
このシステムは、APIのクライアントと、DB という2種類の外部システムに、出力をするシステムです。

プログラムのサブルーチンの場合も同様です。

class Person {
  String name;
  public setName(String name) {
    this.name = name;
  }
}

このコードは、このようにも捉えられますが
f:id:taichiw:20190119002532p:plain

nameはPersonから依存があるだけの別インスタンスなので、このようにも捉えられます。
f:id:taichiw:20190119002715p:plain

逆にDBからデータを読むようなシステムは、「入力が2つある」と捉えることができます。
f:id:taichiw:20190119002824p:plain

バグチケットに各APIのリクエストとレスポンスが貼ってあったらバグの調査は早く終わるのか?

モヤモヤと考えたことを書き出しています。結論ありません。実践もまだしてません。

背景

私は今、こんな感じでInternalなWeb APIが相互作用するサービス*1に関わっています。
f:id:taichiw:20190107184003p:plain

また、こんな感じで組織が別れています*2
f:id:taichiw:20190107184619p:plain

さて、このサービスについて不具合が起票されることがあります。
リリース前のQAだったり、残念ながらリリース後、本番可動しているサービスに対してだったりします。
いずれにしても大事なこととして、不具合はここの視点、最も浅いレイヤである、UIの「挙動が期待通りでない」事象として発見されます。
f:id:taichiw:20190107185147p:plain

一方システム的には、不具合の原因は以下のいずれかになります(複数の組み合わせの場合もある)

  • UI componentにバグが有る
  • API 1にバグが有る
  • API 2にバグが有る
  • API 3にバグが有る
  • API 4にバグが有る
  • DB1のデータが正しくない
  • DB2のデータが正しくない

不具合を修正するためには、この中のどこに問題があるかを見つける必要があります。

どうすれば早く原因が見つかるのか

シンプルに、いずれか一つのプログラム(=UI component または API 1~4 のいずれか)にバグが有るケースを考えます。
バグが有る、ということは外から見たプログラムの挙動として、以下の2つのいずれかに分類することができます。

  • 正しいリクエストを受けているのに、レスポンスが正しくない
  • 正しいリクエストを受けているのに、次のAPIの呼び出しが正しくない あるいは データの更新が正しくない

たとえばAPI 3がバグっていて、「正しいリクエストを受けているのに、レスポンスが正しくない」場合
f:id:taichiw:20190107190453p:plain
API 3が正しくないレスポンスを返すので、それがAPI 1→UI と伝搬することになります。
この場合、API 3を修正する必要があります。

また、API 2がバグっていて、「正しいリクエストを受けているのに、次のAPIの呼び出しが正しくない」場合
f:id:taichiw:20190107191303p:plain
API 4そのものは正しく動作していますが、リクエストが誤っているため期待通りでないレスポンスを返し、最終的にそのレスポンスが原因となってUIの挙動が期待通りになりません。
この場合、API 2を修正する必要があります。

「赤い矢印」が見つかればどのプログラムがバグっているのかわかる

前項で見たとおり、期待通りでないリクエスト、またはレスポンスである、「赤い矢印」
f:id:taichiw:20190107191317p:plain
がどこから始まっているかがわかれば、バグのあるプログラムである、「赤い四角」
f:id:taichiw:20190107191417p:plain
の箇所が特定できそうです。

ということで、バグが起票された際に、全APIのリクエストとレスポンスがチケットに貼ってあれば、即座にどのAPIがバグっているのかわかるのでは? と考えました。
こんな表を自動生成することはできないでしょうか。

API リクエス レスポンス APIへのリクエス
API 1 f:id:taichiw:20190107191851p:plain f:id:taichiw:20190107191902p:plain to API 2 f:id:taichiw:20190107191929p:plain, to API 3 f:id:taichiw:20190107191851p:plain
API 2 f:id:taichiw:20190107191929p:plain f:id:taichiw:20190107192044p:plain to API 4 f:id:taichiw:20190107191317p:plain
API 3 f:id:taichiw:20190107191851p:plain f:id:taichiw:20190107192118p:plain
API 4 f:id:taichiw:20190107191317p:plain f:id:taichiw:20190107191902p:plain

※矢印はイメージです。実際はこの表の各セルに具体的なJsonが書かれます

Spring Cloud Sleuth でTraceIDを発行しているため、一つのUIアクセスから始まる各APIのリクエスト/レスポンスを集めてくるのは技術的には可能そうです。

「赤か青か」を判断するのが実は難しい?

上の表では、API 2は正しいリクエストを受けているにもかかわらず、API 2からAPI 4へのリクエストが間違っています。ここから、API 2にバグが有る、と推測することが可能です。
f:id:taichiw:20190107193039p:plain
では、API 2→API 4のリクエストが間違っている と気づくことができるのは誰でしょうか…。API 2はTeam C、API 4はTeam Dが担当しているので、それぞれのチームのエンジニアに気づくチャンスがありそうです。

API 2の担当エンジニアは気づけるか?

まず、API 2の担当エンジニアは、上の表から以下のことに比較的簡単に気づけそうです。

  • API 2のレスポンスが期待通りでない
  • API 4からのレスポンスが期待通りでない

ここから、API 4へのリクエストが間違っているか、API 4そのものにバグが有る という推測は立てられそうです。
しかし、API 4へのリクエストの検証についてはどうでしょうか。
シンプルな間違え*3ならばすぐ気がつけそうですが、もう少し複雑な間違えですとすぐには気づけないかもしれません。
また、API 2の担当エンジニアが、API 4のリクエスト使用を誤って理解しているかもしれません。

API 4の担当エンジニアは気づけるか?

API 2→API 4のリクエストが間違っている」ということにAPI 4の担当エンジニアが気づくためには、以下のことを理解している必要があります。

なぜならば、バグの起票時点では、UIレベルの情報しかないからです。
f:id:taichiw:20190107193948p:plain
これも、即座に正しいか誤っているか判断できる場合もあると思います。しかし、API 4の使われ方によっては、中間のAPIの仕様を知らないとリクエストの正誤の判断が難しいかもしれません。

リクエストの正しさの確認は狭間に落ちる?

…ということを想像していくと、API 2→API 4のリクエストの正しさの確認というのは、どちらのエンジニアにとっても難しいのでは…? という気がしてきました。

レスポンスの正しさの確認のほうが簡単かも

では一旦、「すべてのAPIのリクエストは正しい」と仮定して、レスポンスの正しさだけに注目したらどうでしょうか。

バグの起票時に、UIレベルで「本来Aという振る舞いをすべきところ、Bという振る舞いをしている」ということは記載されています。
この場合、「『B』の原因となるレスポンスを自分のAPIが返している」かどうかの確認は、リクエストの確認の正しさに比べると比較的難易度が低い …気がします。 自分の経験的には。

APIのレスポンスだけとりあえず並べてみる

レスポンスの正誤の判別は比較的簡単… という前提で、「バグ起票時に各APIのレスポンスが自動的に貼り付けられるツール」のようなものを想像してみます。

API レスポンス
API 1 f:id:taichiw:20190107191902p:plain
API 2 f:id:taichiw:20190107192044p:plain
API 3 f:id:taichiw:20190107192118p:plain
API 4 f:id:taichiw:20190107191902p:plain

※繰り返しになりますが、矢印のところには具体的なJSONが入るイメージです

システム構成が頭に入っていれば、以下のような図が見えるわけです。
f:id:taichiw:20190107195535p:plain
※データがおかしい可能性もありますが、今は除外して考えます

API 3には絶対バグがないことがわかった!バンザイ! といいたいところなのですが、
UI, API 1, API 2, API 4 のどれもバグ持ちの可能性が消えていません。
これだけでは、あまり何かが解決しているようには思えません。

いろいろ考えてみたけどこれだったら結局…

先程問題にあげた、「リクエストの正誤の方法」について掘り下げたほうが何か効果があるかもしれません。

今日はここまでです。

続きます…(多分)

*1:図は簡略化しています。現物はもっと大きいです。また、いわゆるマイクロサービスアーキテクチャに近いとは思いますが微妙に違う気もします。組織とか。

*2:この組織構成が良いのかどうかの話は、ここではしません。

*3:例えば、API 1 -> API 2 では Item A がリクエストされているのに、API 2 -> API 4 ではItem Bがリクエストされている というケースです。