覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。 twitter: @yyoshikaw

運用から見たシステムに関してのあれやこれや

ITシステム(サービス, アプリケーションレベルで)の開発を行っていると、設計してコード書いてテストしてリリースするという流れになり、 このリリースというのがゴールという感覚に陥る場合があります。

実際は、システム(サービス)は運用が開始されてからがスタートだと言っていいと思います。

が、私含めた開発者というのは往々にして運用フェーズに対して意識がいきにくい気がします。
DevOpsが広まった世の中ではそんなことないんですかね?私の感覚が古すぎ?)

これまでの経験から、運用観点でシステムがどうあるべきか、開発者が何を意識しておくべきなのか を書きました。
(思いっきり個人的な見解なので、システムやサービスやビジネスモデルによって当てはまったり当てはまらなかったりだと思います)


実行環境

  • 実環境とQA環境は一致していない
    • 開発環境やQA(品質保証)環境が実際のサービスが動作する環境と(スペックやリクエスト量が)同等になっている状況はほとんどありません。
      自分の開発環境やQAチームの環境で問題が出なくても、実環境では問題が出ます。
  • ハードウェア
    • ITシステムは当然ですが、ソフトウェアだけではなくハードウェアがあることで成り立っています。
      クラウドになろうが仮想化されようがその下支えとしてハードウェアが存在する事実は変わりありません、ハードウェアは経年劣化したり破損したりが当然のようにあります。
      ファンが故障したり、ディスクが読めなくなったり、マシンの電源が入らなくなったり。
      ハードウェアの影響を受けて性能が上手く出せない、予期せぬシャットダウンを余儀なくされる等の試練をソフトウェアは受けることになります。
  • 実環境とステージング環境を区別できるように
    • コンソールでステージング環境(テスト環境)に接続して作業しているつもりが、実際には実環境(本番環境)につないでました。というケースがあります
      そうならないためにも、実環境とステージング環境では接続までのステップを変える。接続した際のターミナルの背景を異なる色で表示する等、明確にどこにつないでいるかが分かるようにした方が良いです


ユーザ

  • 大事なお客様は攻撃者
    • システム(サービス)は利用ユーザが存在して(お金を払ってくれるから)こそ存続することができます。
      しかし、ユーザは意図せずシステムへの攻撃者ともなります。大量のリクエストを投げてきたり、思いもよらない巨大なデータのダウンロードをしたり…
      とにかく、この意図しない負荷にシステムは耐える必要があります。
  • やらかしているユーザを特定できるようにする
    • 上に書いた通り、ユーザは意図しないにせよ意図したものであるにせよシステムに対して負荷をかけてくる場合があります。
      そういったユーザに対しては何らかの対処が必要になり、対象のユーザを把握する必要があります。
      そのためにも対象のユーザのユーザIDや送信元のIP等がログに記録されている必要があります。
  • ユーザIDを共有させない
    • いくら対象ユーザを特定しようとしても複数ユーザ間で1つのユーザIDが共有されていると特定が困難になります。BtoBのシステムでこの状況に陥る場合があります。
      必ず個人単位に割り当てられたユーザIDのみが利用されるシステムである必要があります。
  • 優先すべきユーザ
    • 多くの代金を支払っていたり紐づく実利用者が多い等のビッグユーザも存在すれば、そうではないユーザもいます。やはり、ビッグユーザの方がサービス運営には大きなインパクトがあります。
      規模の小さなユーザを軽んじるという意味ではないですが、サービスを運用・開発する側としてはユーザ対応に関して戦略的な優先度付けは必要になります。
  • ITリテラシーは人それぞれ
    • サービスの利用者はITリテラシーが高い人だけとは限りません。色々な人が使うことを理解したうえで、ユーザのサポートをする必要があります
  • ユーザの無効化
    • システムを利用しなくなったユーザを削除または無効化するための仕組みは必須です


現場での障害・エラー

現場(実環境)では、実装やユニットテスト時には想定しにくいエラーパターン(障害パターン)が存在しています。
アプリケーションレベルでは対応できないものも含まれますが、システム全体としては考慮しておいても良いと思います。

  • ディスクフルになって書き込みができない
  • ディスクがunmountされている
  • inodeが枯渇
  • ファイルシステムが破損
  • ファイルシステムがreadonlyになった
  • スレッドが枯渇
  • メモリの枯渇(OutOfMemory)
  • 他システムから仕様に違反した電文が送られてきた
  • リクエスト先が無応答
  • 無限や非数を表す数値データが送られてきた
  • マスターとスレーブのDBに差分がある
  • データベースに仕様と異なるデータが混入している
  • 証明書の期限が切れた
  • JavaVMがクラッシュ
  • 設定ファイルに書かれた設定内容の整合性が取れていない
  • (機器の不具合で)稼働後に一定日数経つと機器が勝手に再起動する
  • bindしたいポートが重複
  • システム日時がずれている
  • シーケンス番号がオーバーフロー


ログ

  • ログが頼り
    • ログを出力しないアプリケーションも存在しますが、基本的にシステムの状況を知るための入口となるのはログです。
      エラーが発生した状況などではとにかくログが手掛かりですので詳細な状況を記録する必要があります。
  • 整形
    • 基本的に同一システム内では統一されたフォーマットでログを出力したほうが良いです。フォーマットが異なるとログを解析する労力が大きくなりやすいです
  • コンテキスト(文脈)を出力
    • エラーをログに記録する場合は、発生したエラー内容だけではなく、その現象を取り巻くコンテキストの情報についても出力するべきです。(いつ、誰の操作で、どんなイベントやデータが関連しているかetc)
      そうした情報がある方が、原因を追いやすくなります。
  • 単位も出力
    • 何らかの数値をログなどに出す際は単位も出力するべきです。単位がbyteなのかMBなのかで話は大きく変わってきます
  • 個人情報
    • システムの特性にもよりますが基本的にログに個人情報は出力するべきではありません。出力するにしても暗号化するかマスク処理は必須です
  • Diskを消費する
    • ファイルにログを出力しているとすれば、出力すればするほどDisk容量を食うことになります。ログもリソースを消費することは意識しておいたが方が良いです
  • パフォーマンスの劣化
    • 非同期型ではなく同期型のロギングを行っている場合は特に、ログを出力すればするほとアプリケーションの性能劣化につながる場合があります。ログの出力のし過ぎが害になる場合があります。
  • エラーは記録した、でも誰も気づかない
    • アプリケーションでエラーが発生して、ログファイルに記録した。が、誰もこのエラーに気づいていない。
      この状態は最悪で、何の意味もありません。
      エラーが書かれたことを監視する仕組みや通知する仕組みなど必須です。
      誰の目にも触れないものは存在していないのとほとんど同じです


画面

  • ユーザが一番触る場所
    • システムが提供する画面は、ユーザが最も頻繁に触れる場所となります。ということは、ここを通じて何らかのエラーが発生することになります
      画面そのものがおかしな動作をする場合もあれば、バックエンドの仕組みに悪影響を与えることがあります
  • 画面はシステムの顔
    • システムは画面だけではなくグラフィカルなUIを持たないバックエンドのサービスや色んな物の組み合わせで成り立っています。画面はシステムの一部でしかありません。
      が、ユーザが直接見るのは基本的に画面です。そのためユーザは画面の操作性やカッコよさ(or ダサさ)を通じてシステムの良し悪しを判断することがあります


分散

  • 相関ID
    • 複数のサブシステムやサービスに分かれてシステムが成り立っている場合、特定のリクエストに対する各サービスの一連の処理を串刺しで確認したい場合があります。そのために、リクエストID(correlationId)等を発行してそのIDを各サービスに引き渡した方が良いです。
      各サービスはそのIDをログに出力します。そうすることで繋がりを把握することができます。
  • オフラインでも動作してほしい
    • 分散したサービスが協調している場合、リクエスト先のサービスが落ちていることも十分にあります。相手先が落ちている状態でも自分自身のサービスは正常に動作し続けるべきです
  • 処理のブロッキング
    • 意識してプログラムを書かない限りほとんどの場合は処理をブロックするコードを書くことになります。
      分散して非同期で動作して協調するシステムの場合ブロッキングされている箇所は、ボトルネックになり問題を引き越す箇所になりやすいです
  • 物理的な距離
    • データセンターを複数個所においてレプリケーションしている場合に、データセンターの物理的な距離に応じてデータが書かれてから冗長性が本当に確保されるまでに時間がかかることがあります
  • 障害の発生個所と原因箇所
    • システムが複数のサービスに分割している状況では、障害が発生する(目に見える形でエラーが発生する)箇所と原因箇所は離れていることが往々にしてあります
      障害発生個所周辺だけを追っても原因にたどり着かないことがあります


バグのないシステムは無い

アプリケーションにバグがあるのは当然ですが、OS(カーネル)やミドルウェアやその他ファームウェアにも当然のようにバグはあります。
それらに対してどう付き合っていくかは当初から計画しておくべきです


ネットワーク

  • ボトルネックになる
    • システム内で最もボトルネックになりやすい場所かもしれません。他の場所で性能が出ていてもネットワーク起因で上手く性能が出せないことは多々あります。
  • 信頼してはいけない
    • ネットワークはシステム内で最も信頼してはいけない場所です。運用中に何らかの通信が失敗する状況は必ずやってきます。


データベース

  • ボトルネックになる
    • データベースは基本的にボトルネックになりやすい場所です
  • トリガー
    • 開発者が意図しないトリガーを運用チームなどがDBにしかけてしまうと予期せぬ動きをすることがあります。把握していないトリガーが存在していると相当な混乱を生みます
  • データ量
    • テーブルのレコード数が極端に増えた場合にSQLの実行計画が全く意図しないものになる場合があり、突然性能劣化する場合があります
  • 遅いクエリを監視する
    • 遅いSQLがあると、システム全体がその遅さに引っ張られる場合があります。遅くなかったSQLがデータ量の増加などによって遅くなることがあります。遅いSQLが実行されていないか監視すべきです


できるだけ自動化

  • 可能な限り変更操作のSQLを直接実行させない
    • データのメンテナンスが必要で、運用チームが手動でSQLを実行するケースがあります。
      ただし、人間が介在するれば当然操作を誤る場合もあります。アプリケーションとしてのデータの整合性がとれない状況も発生します。
      可能な限りアプリケーション側でデータメンテナンスの仕組みやツールを提供するべきです
  • 手動の作業はスケールしない
    • 人の手が必要な作業はスケールしません。システムが大規模化していくと人手がかかる作業は漏れていく場合があります
  • 一度限りと思っていた作業も
    • “こういうデータを抽出したい"という運用観点での要望が良くあります。一度限りならその場限りのSQLを組み立てたり、手動でデータを整形したりでも良いでしょう。
      同じ依頼が再度来たらシステム化またはツール化することを検討した方が良いです。3度目4度目の依頼が来るのは確定です。
      最低限でも手順化して、他の人にも任せられるようにした方が良いです


エラーコード・メッセージID

エラーや障害があった場合に、そのエラーを一意に特定するエラーコードやメッセージIDをログや画面に表示するべきです。
詳細なエラー内容やメッセージの文面しかないと担当者間で誤った伝言ゲームが発生して状況が正しく伝わらない場合があります。
状況を一意に示すID等があった方が伝達はしやすいです。また、国際化対応しているシステムの場合にもIDやコードだけ分かれば状況を把握しやすくなります。


データ

  • 稼働当初が一番データが少ない
    • ほとんどのシステムがシステム内に蓄えられるデータが増加していきます。稼働の最初が最もデータが少なかったという状態が普通です。
      当初のデータ量なら動いていても、1年後5年後のデータ量では性能が出なくなることもあります
  • データの増加傾向を監視する
    • 定期的に日単位、月単位、年単位でデータの増加量を監視するべきです。ディスクの残容量との兼ね合いもありますし、意図しない怪しい増加が無いかを確認しておくことも重要です。
  • 削除する仕組み
    • データを保管するというのもタダではありません。扱っているデータのサイズが巨大化した場合に必要のないデータを削除して、取り扱う量を減らす仕組みを検討すべきです
      仮に削除しないにしても、アクセス頻度の低いデータは安価な保管手段に変更するなどの検討をする必要があります
  • 去っていくユーザのデータの取扱い
    • 提供しているサービスに不満がありユーザが契約を切る(契約を更新しない)場合もあります。そうしたケースにおいてシステム内に蓄積された対象ユーザのデータをどう扱うのかは当初から取り決めておく必要があります
  • テストデータ
    • 運用時は利用しないが、運用前の試験時に負荷をかける目的などでテストデータを用意する場合があります。
      このデータが誤って運用中にシステム内に送られてしまうケースがあります。できればテストデータのID等からフィルタリングできる仕組みなどを設けておいた方が安全です


データ移行

完全に新規のシステムやサービスで無ければ基本的に何らかのデータ移行が発生します。

移行のパターンとして以下のようなものが考えられます。

  • 他システムからの移行
  • 他システムへの移行
  • 既存システムから新システムへの移行

データ移行に関しては、移行元のデータをどのように受け取るのかとどう出力するのかのインターフェースが重要です
また、インターフェース以外にも以下の点についても考慮が必要です

  • データ移行にかかる費用。その費用を誰が負担するのか
  • データ移行にかかる期間
  • データ移行時にシステム停止が伴うか?
  • データ移行が正常に完了したこと(移行元と移行先で不整合になっていないか)をどのようにチェックするのか?


結合・接続部分

他システムや他プロダクトの結合点・接続部分は色々と考慮してもしきれません

  • 絶対に問題が発生する場所
    • 他システム・他サービスなどと連携している箇所は運用後に絶対にエラーが発生します。結合部分でのエラー処理は必須です
  • 意図しないエラー
    • 他システムとは取り決められたインターフェースでやり取りします。決められたエラーコードなどに基づいたエラーハンドリングも組み込むでしょう。
      が、運用後には取り決めに無かったエラーが突然返ってきます。それにもアプリケーションは対処できる必要があります
  • 耐えられない量のリクエスト
    • 誤って大量のリクエストを投げる場合もあります。リクエストを受ける側は何らかの流量制御して自分自身がダウンしないようにする必要があります。
  • タイムアウト
    • リクエスト先からエラーが即時に返ってくれば良いですが、場合によってはいつまでたっても応答が無い場合もあります。必ずタイムアウトできる仕組みを入れるべきです。
      そうしなければスレッド全部を使い切ってサービスがダウンすることにもつながります。


リトライ

  • 短い間隔で無限リトライすると攻撃と同じ
    • リクエストを受ける側のサービスがダウンしている場合に、クライアント側はリトライすることでしょう。
      しかし、短い間隔で無限にリトライを繰り返すとシステム全体への負荷になり、場合によってはシステムをダウンさせる原因にもなります。
  • べき等性
    • 何らかの理由でクライアント側が全く同じリクエストを複数回送信してくることがあります。リクエストを受けた側はそれでも正常に動作して同じ結果を生み出すべきです


監視・復旧

  • システムの負荷の周期性
    • 基本的にはシステムにかかる負荷(CPU負荷、ネットワークトラフィック、メモリの使用量・・etc)には、一定の周期があります。(平日のこの時間帯はCPU負荷が高い、夜間の特定時間帯はネットワークトラフィックが多い 等)
      その周期性から外れた予想外の負荷状況が発生していないかを日単位、週単位、月単位で確認した方が良いです。
  • 自動復旧
    • よほど単純なサービスやアプリケーションでなければ、長期間稼働する中でダウンする時はダウンします。
      重要なサービスであれば、対象のサービスのプロセスを監視して、ダウンしていれば自動復旧させるなどの仕組みを検討した方が良いです。
  • ユーザより先に気づく方が良い
    • 監視の仕組みでシステムの障害予兆を把握できた方が、ユーザから連絡により障害を気づいた場合よりも対処のコストは圧倒的に下がります。
  • 監視サービス自体がダウンしないように
    • システムを監視する側のサービスがダウンしてしまうと、システムが本当に正常に稼働しているのかどうか分からなくなります。
      監視サービス自体がダウンしない(ダウンしても自動復旧する)ような仕組みが必要です


アプリケーションの実行状況把握

  • ヘルスチェック
    • アプリケーションが正常に稼働しているかどうかをチェックするための口を設けた方が良いです。監視サービスから定期的にそのヘルスチェックを実行するのも一つの監視となります。
  • 処理の進捗状況
    • 時間のかかる処理や、大量のリクエストを扱う処理などは、その処理の進捗状況(処理が詰まっていないか)が分かる仕組みがあると良いです。
      キューの残数、ダウンロード状況など。


バックアップ

  • リストアできないと意味が無い
    • 定期的なバックアップはシステムにとって必須です。バックアップはできている(はず)だが、そのバックアップしたデータでリストアはできますか?
      もしくはリストアするのにどれだけの時間を要しますか?リストア中に発生した整合性の取れていないデータを整合が取れる状態にできますか?
  • バックアップ処理による負荷
    • システムによってはバックアップするデータのサイズが巨大化する場合があります。バックアップ処理そのものがシステムに負荷をかける場合もあります


単一障害点

その一箇所が動かなくなるとシステム全体がダウンするような単一障害点(Single Point of Failure)は当然ない方が良いです。冗長化するなど何らかの方策が必要です。
この点はシステム上の話だけではなく、人にもいえます。対象のサブシステムについて詳しい人が一人しかいない場合、その人が長期休暇を取るのが危険という場合があります
日ごろから人間側の冗長性も確保すべきです


スケール・国際化

  • 最初からスケールする仕組みを組み込む
    • システムのスケールについては当初から計画しておく必要があります。後付けでのスケールはかなり難しくなります
      1000リクエストに対応できていたサービスにおいて、(後付けで)サービスが稼働するサーバの台数を単純に増やしても1000万リクエストには耐えられない場合があります。
  • グローバルにやる予定なら
    • 対象のサービスをグローバルに展開する予定があるのなら最初から国際化の対応をしておくべきです。後付けで国際化しようとするのは結構な労力になります
  • 展開する地域が増えたときのリクエスト量の増加
    • サービスを展開する国や地域が追加された時に、リクエストの増加がそれまでの伸びとは関係なく激増する場合があります。
      例えば中国などの人口の多い地域に展開した際にはそのような可能性があります。


法的要求事項・ガイドライン/監査

  • 要求事項
    • システムによっては法的な要求事項や国が定めたガイドラインに準拠しなければならない場合があります。
      これらの内容も年々変わっていく場合があり、追従していく必要があります。
  • IaaS系クラウドサービスの利用
    • クラウドサービスによっては、物理的なサーバが隠蔽されているだけではなくそのサーバが存在するデータセンターの所在が非公開になっている場合があります。
      通常は特に問題になりませんが、データセンターの監査を受ける必要が出てきた時にこれではNGとなる場合があります(所在不明のため監査ができないため)
  • 法によるデータの存在場所のしばり
    • システムにかかわる法律やガイドラインによって、データを管理してよい場所(国)が限られる場合があります(例えば、日本にしかデータを置いてはいけない等)
      このような場合は例えば、クラウドで指定するゾーンとしてアメリカなどを指定できない場合があります
  • 監査ログ
    • ユーザ操作を監査ログとして永続化して通常のアプリケーションログとは別に管理する場合があります。監査ログを追うことでユーザが何をしていたのかが明らかになります。
  • 法定停電
    • ビルなどは点検のために定期的に停電する日が必ず来ます。ユーザー環境にシステムに関する機器があるなら、電源が落ちる日が定期的に来ます。

知識の共有

  • リリースした機能を共有する
    • 開発チームと運用チームが分かれている場合に、新しくリリースされたアプリケーションの変化を運用チームが把握できない場合があります。
      定期的にチームを超えて情報を共有する場を設けるべきです
  • 暗黙知を無くす
    • サービス稼働後に走りながら障害対応やデータメンテナンス等を行っていくと暗黙知が溜まっていきます。暗黙知が当然の状態になってしまうと、チームに新しい人が加わっても上手くパフォーマンスが発揮できません。
      得られた知見はドキュメト化してチーム内に共有する必要があります
  • 現場の対応をドキュメント化
    • 運用中に緊急でその場限りの対応(設定を変更した、再起動のcronを仕掛けたetc)をする場合がありますが、そのような対応が結果的に恒久策になっている場合があります。
      ただ、これらの対応がドキュメント化されていないと後の運用・保守において混乱を招くことになります。


コスト

  • 運用にかかったコスト
    • クラウドのサービスに払った費用だけではなく、パッチを当てる対応や障害時の対応等にどれだけの人的リソースが投入されたかも把握しておくべきです
  • サービスによってコストが違う
    • IaaS系クラウドサービス等は、サービスごとに発生する費用のモデルが違います。CPUの使用時間、データの蓄積量、通信量など何に対して課金されるのかが異なってきます。
      提供するビジネスモデルに応じてコスト計算しなければ、とんでもない費用を払わなければならない事態になる場合もあり得ます


システムへ影響を与えるシステム外のイベント

アプリケーションやシステムそのものではどうにも対処しきれないが、場合によってはシステムに悪影響を及ぼすものがあります

  • 落雷
  • 停電
  • 地震
  • 洪水
  • 火災
  • 空調の故障による温度異常
  • ネットワークケーブルが切断
  • 電圧降下

必ずしも悪影響を与えるわけではないが、念頭に置いておいた方が良いものとして以外

  • うるう年
  • うるう秒
  • 年号が変わる


まとめ

ユーザの要求を満たす機能を提供するプログラムを書くだけでは、システムを作り上げて運用に耐えられるということにはなりません。
もう少し高い位置からの視点が必要になりますね。

色々雑多な感じで書きましたが、ものによってはシステム稼働後に追加で対応できるものもあります。しかし、稼働前から考慮しておかないと厳しいものもあります。



関連エントリ

INEVITABLE ja night インターネットの次にくるもの #inevitable2017

INEVITABLE ja night インターネットの次にくるものに参加してきました。

以下自分用のメモです。


概要

Google Cloud に代表されるクラウド技術の進化によって引き起こされるその先の世界を、
機械学習、VR / AR、IoT などの領域で活躍されているスタートアップの方々と一緒に議論する

  • 開催日: 2017/6/12
  • 場所: ザ・プリンスパークタワー東京


「不可避な未来」x「NEXT 5 BILLION」対談

服部 桂 氏(ジャーナリスト), 北浦 健伍 氏(AGRIBUDDY LIMITED), 小島 英揮 氏(パラレルマーケター)

f:id:nini_y:20170612183919j:plain

  • 攻殻機動隊の世界観がリアルに
  • クラウド以降のエコシステムが創った不可避な流れ
    • Cloud ⇒ Big Data, Mobile ⇒ AI, VR, IoT
    • エコシステムの延長としてAI, VR, IoTが来ている。ただのトレンドではない
  • 日本における不可避な流れ=人口の減少
    • 労働人口、消費人口の減少。このままだとGDPの減少
  • 重要な市場:NEXT 5 Billion
    • エコシステムの拡大余地
  • 「THE INEBITABLE」 KEVIN KELLY
  • Agribuddy
    • 25億人が新興国小規模農家
    • 我々の胃袋は新興国小規模農家によって支えられているが、小規模農家はみんな貧しい
    • データがあれば正しい処方箋を作成できる
      • 信用 = 過去の履歴 × 将来の予測
  • AI, VR, IoTの中で期待するものは?
    • VR
      • 知識ではなく感覚の共有。テキストベースではできなかった体験やコンテキストを共有する
    • Cloud as Real Corpus
      • AIに食わせる燃料がVRやIoTによって吸い上げられる。昔に比べて巨大なエネルギー(データ)が集まっている
      • 吸い上げられルデータ量が桁違いに増えて、現在のビジネスモデルでは扱えないかも
      • 恣意的ではない自然なデータが集まることが重要。そのためにIoTやVRが重要
    • 日本市場が縮小していく中でエンジニアや起業家に求められるものは?
      • 海外では日本のような空気を読むことはないので、伝えることの重要性が高まる
      • コンテキストや感情を新しいツールやサービスで伝えるということが今後のアプリケーションに求められる
      • Next 5 Billion のために、文字が読める前提というのが覆さなければならないかもしれない
        • 文字ではない別の表現で伝える必要性がある。頭の中のイメージをそのまま伝える
      • これからの起業の拠点は日本ベースがいいか、世界に出てしまうのが良いか?
        • 顧客がどこにいるかが重要
        • 体制が出来てからハードルを越えるのは結構厳しい
          • 日本で上手くいったビジネスを世界に持ち出すのはあまり考えない方がいいかも。
          • 世界に出るなら最初から日本の常識を無視したほうがいい
        • 近代的な人間が創った国という概念が壊れるかもしれない。国を超えた状態で考える必要がある
        • 日本のパスポートは世界でも指折りのもので、世界に出ていきやすい通行手形。そういう意味では有利
  • Next 5 Billionは今のところはがら空き。フロンティア。
  • 未来は想定外が必ずある。コンピュータによって創造力のオプションを増やす。
  • 大きいものが小さいものに勝つのではなく、速いものが遅いものに勝つ


Google のプロダクト開発

徳生 裕人 氏(グーグル合同会社)

  • Next Billion
    • Mobile First & Mobile Only というユーザが今後のターゲット
    • 新興国は一つのステレオタイプが当てはまるということはない
    • 接続状況、端末スペック等は日本などに比べると圧倒的に劣る
    • 少ないメモリでも動作するAndroidを作るしかなくなった
    • みんなにとっての新しい市場。全員にとってスタートライン
  • Machine Intelligence
    • 新しい技術によるサービスをユーザに届けるタイミングが重要
    • Smart Reply
    • Voice Recognition 雑音下でのエラー率が低減
    • 各自が機械学習の素養を持つことが重要になっている
      • Google全社員に機械学習の研修を受けさせようとしている
  • Google Assistant
    • Google Lens
    • Google Assistant
      • 今後色々なデバイスと組み合わさっていく必要がある
    • Actions on Google
    • 世界中のプロダクト・サービスとの連携


IoT、AI、VRの未来を語るディスカッション

玉川 憲 氏(株式会社ソラコム), 岡田 陽介 氏(株式会社ABEJA), 芳賀 洋行 氏(InstaVR株式会社), 及川 卓也 氏

  • SORACOM
    • 日本発でグローバルに通用するプラットフォーム
    • 通信の民主化
    • 通信をセキュアに管理できる仕組み
    • ソフトウェアで作り上げたバーチャルキャリア
  • ABEJA
    • 日本のTOPレベルの教授等と組んでいる
    • NVIDIAと組んでいる
  • InstaVR
    • VR制作は一般的にはコストが高い
    • InstaVRはVR制作工数を削減する
    • 採用10000社
  • IoT, AI, VRについて
    • IoT
      • DeepLearningやる上では必須。データが爆発的に増えるので学習データが増える
      • エッジ側でのAIも重要。工場や小売店の中でAI
      • IoTの技術は発展途上。今年、来年にはモノ向けのクラウドが進化していく
    • AI
      • 人間には発見できなかった最適解を見出すのが楽しみ
      • VRのヘッドトラッキングデータがAIに利用できるのではないか
      • ABEJAは基礎アルゴリズムも作りながら、だれでも使えるサービスなども提供している
      • 一番のボトルネックは通信
    • VR
      • 以前に比べて圧倒的にVRは民主化された
      • Virtual=本質。ユースケースによって本質は異なる。今後はもっと本質が伝えられるようになる
      • 製造業等の職人に独特の感性がある。その感性をVRで伝えるのに有用なツールとなりうる
        • AI化した知見をVRを通じて人に継承する
      • ヘッドセットも今後もっと多く出てくる可能性が高い
  • 起業という選択について
    • InstaVRは会社ができたのはプロダクトよりも後でした
    • ソラコムは、仮想のプレスリリースから先に書いてそこから起業
    • 買収したい企業はあるか?
      • 起業家精神を持っていることと技術力の高さが合わさることが重要
      • 勝つために必要なコンポーネントであれば買収することはある。逆に買われる場合もあると思う



関連エントリ

Javaでプログラムを書く際に意識しておきたいこと

以下、個人的にJavaでプログラムを書く際に意識しておきたいことです。

ただし、学術的な裏付けなどがある内容でありません。あくまで私の経験に由来する内容となっています。

そもそもコンテキストによってはそぐわない内容もあると思いますので、その辺はうまいことスルーしてもらえたらと思います。


Collection

空のList

メソッドの戻り値として空(size==0)のListを返却する場面がありますが、その場合はCollections.emptyListを使うのが良いです。
new ArrayList()でListを生成してreturnするよりも、処理も早くコードの意味も分かりやすくなります。
ただし、このメソッドで返されるListはImmutable(不変)であることを理解しておく必要があります。
Collectionsクラスには、空Setや空Mapを返すメソッドも用意されています。

大量データをListに格納する場合はサイズ指定

ArrayListのデフォルトコンストラクタで生成されるインスタンスのデータ格納容量は10です。
この値よりもはるかに大量のデータを格納(add)することが確定している場合は、最初からサイズ指定のコンストラクタを使うべきです。
基本サイズ(10)を起点にしてデータを格納を繰り返すと容量拡張が何度も実行されパフォーマンスに悪影響を及ぼす場合があります。
(C言語でいうところの、mallocをしたあとにreallocを繰り返しているような感じになってしまいます)

EnumMap

Enum(列挙型)をキーにしたMapを作成したい場面がありますが、そのような場合はHashMapではなくEnumMapを使用した方がよいです。
HashMapを使用するよりも実行効率が良くなります。

Vector、Hashtableを使用しない

java.util.Vectorjava.util.Hashtableは同期化されたCollectionですが、性能などの問題もあり使用しないようにするべきと一般的に言われています。
同期化が必要ない場面ではArrayListHashMapへの置き換えを行うべきです。同期化が必要な場面でも別の代替手段を検討すべきです。
よほど理由がない限りVectorHashtableを使用しない方がよいです。

サードパーティのCollectionライブラリ

Java標準のコレクションフレームワークの実装は古いと言われ、問題も抱えています。
(プリミティブ型をそのまま格納できない、メモリ効率が良くない、コレクションの種類が貧弱、、、etc)
標準のColletionの問題等を解決するために以下のようなライブラリが存在しています(標準のCollectionでパフォーマンスで問題となった場合や使い勝手が悪い場合などに利用を検討するのがよいです)


メソッド

戻り値にnullを使用しない

return nullとなっているメソッドは、メソッド利用側でnullチェックが必要となり、チェックを忘れるとNullPointerExceptionを引き起こす場合があります。
メソッドを作成する際に出来る限りnullをreturnしないように心掛けた方が良いです。
戻り値の型がListなら空Listをreturnする。戻り値の型が配列なら要素数0の配列を返却する。それ以外の場合はNullObject(等のデフォルト動作をするオブジェクト)、Optionalの導入を検討する。
どうしてもnullをreturnする必要がある場合は、javadocコメントにどういったケースでnullがreturnされるかを記述するべきです。

引数でnullをとったら即時にエラーを通知する

一般的なほとんどのAPIにおいて引数にnullを渡されるというのは異常ケースです。(API利用側のバグと言えます)
業務の(publicな)メソッドで引数にnullをとった場合もnull用の特別の業務処理を行うのではなく、即時にNullPointerExceptionIllegalArgumentExceptionで呼び出し側にエラーを通知するようにした方が良いです。
null値をAPI間で引き回すと処理が分かりにくくなったり、バグが混入しやすくなります。
(もちろん、nullを特別に扱わなければならないケースもあるとは思いますが、そのようなケースは稀です)


マルチスレッド

スレッドセーフではない危険な標準クラス

スレッドセーフだと思って使用するとマルチスレッド環境下で思いもよらない例外が発生するクラスがjava標準で存在します。

  • java.text.SimpleDateFormat
    • 有名ですが、SimpleDateFormatはスレッドセーフではありません。staticなメンバとして保持したりすると予想外の動きをします
      対処法としては以下
      • commons-langのFastDateFormatを使用する
      • ThreadLocalにして使用する
      • 処理のたびにnewする
        • ただしSimpleDateFormatのインスタンスを生成(new)するコストはかなり高いです。
  • java.text.NumberFormat
    • NumberFormatのjavadocを読むとわかるのですがマルチスレッドでアクセスする場合は外部的に同期化する必要がある旨が記載してあります。
  • java.text.DecimalFormat
    • NumberFormatと同様です
  • java.text.MessageFormat
    • NumberFormatと同様です

スレッドセーフの表明

JSR305のアノテーションを使用することで対象クラスがスレッドセーフであるか、そうでないかを表明することができます

  • @ThreadSafe ・・・ スレッドセーフであることを表明するアノテーションです
  • @NotThreadSafe ・・・ スレッドセーフではないことを表明するアノテーションです

(上記アノテーションはこの本に由来する)


例外

例外の翻訳(exception translation)

例外をスローする側とキャッチする側のレイヤーが異なる場合などは特に、抽象概念に適した例外を投げる必要があります。
たとえば、何らかのデータにアクセスするオブジェクトが、SQLExceptionFileNotFoundExceptionIOExceptionの3つをスローするよりも
それらをラップしたDataAccessExceptionをスローする方が上位側も扱いがやりやすくなります。
(本件は「Effective Java」の項目61にも同様の内容が書かれています)

複数の例外をスローしない

「例外の翻訳」と同様ですが、1つのメソッドから複数の例外をスローするのは得策ではありません。
そのメソッドを利用する側からすると迷惑でしかありません。極力1つ(or 2つ)の意味のある例外をスローするようにすべきです。
複数の例外をスローするメソッドを利用する側は、java.lang.Exceptionでまとめて例外をcatchをしたくなってしまいます。

  • よろしくない例
// 本メソッドの呼び出し側はそれぞれの例外を個別にcatchする必要がある
public ServiceResult doService(ServiceRequest request, User user) throws NotCertifiedException, CertificateException, SQLException, SocketException {
    
    // do something
}
  • 改善例
// 本メソッドの呼び出し側は複数の例外を個別にcatchする必要がない
public ServiceResult doService(ServiceRequest request, User user) throws ServiceException {
    
    // do something
}

標準例外を使用する

外部向けのAPI等では特に以下のようなJava標準例外を積極的に使用することでバグを生みにくくなります

クラス名 利用シーン
IllegalArgumentException 引数が想定しない値である場合などにスロー
NullPointerException (引数などがnullで)null操作が発生してしまう場合などにスロー
IllegalStateException 実行すべき状態が不整合である場合などにスロー
UnsupportedOperationExcption 対象の操作を提供しない場合などにスロー
IndexOutOfBoundsException 範囲外の要素にアクセスした場合などにスロー

チェック例外 vs 実行時例外

Javaで定義する例外は、Exceptionクラスを継承したチェック例外(検査例外)とRuntimeExceptionクラスを継承した実行時例外の2つに分かれます。
チェック例外を定義した場合、例外のハンドリングをしているかどうかをコンパイラでチェックすることができます。
これは一見、例外のハンドリング漏れがなくなるので良い事のようにも思えるのですが、
メソッドで上位に例外をスローしていくような場合は、例外がスローされる通り道となってしまう中間のメソッドにおいてもthrows節に対象例外を記述する必要があり、
これは対象メソッドが関心のない例外によって汚されているとも考えられます。
また、チェック例外はJava以外の言語ではほぼ見られない機構であり、本当に必要なものかは若干不明です。
例外クラスを定義する場合に、何も考えずExceptionクラスを継承するのではなく一旦上記のような問題点等があることを考慮した方が良いです。
Javaのチェック例外という概念がJava誕生からこれまでの歴史をもってしても、今なお他言語や他プラットフォームに普及していません。
チェック例外が評価されていない(扱いにくい等の負の側面が多い)という事実だと思われます。

CloneNotSupportedException

Object#cloneメソッドがスローするCloneNotSupportedExceptionはチェック例外ですが、
スローされる原因はCloneableインタフェースの実装し忘れによるものです。そのような状況はコードとしてのバグと言ってもいいと思います。
また、この例外をスローされた側はcatchしてもほとんどの状況で有用な処理はできないはずです。
cloneメソッドをオーバーライドする場合はCloneNotSupportedExceptionを盲目的にスローするのではなく、
IllegalStateExceptionRuntimeException等の実行時例外でラップ(またはInternalErrorで例外メッセージをラップ)してスローし、 cloneメソッドの利用側の負担を減らすのが良いでしょう。
(正直、CloneNotSupportedExceptionがチェック例外である理由は基本的にないと考えられます。cloneメソッドはチェック例外をスローしているために扱いにくいAPIとなっている一例だと思います)

UnsupportedEncodingException

String#getBytes等のメソッドで本例外がスローされる場合があります。
CloneNotSupportedExceptionと同様にチェック例外となっていますが、この例外をcatchしても業務アプリケーションとして有用な処理はほぼできないと考えられます。
(そもそも、キャラセットを"UTF-8"等と指定すれば、UnsupportedEncodingExceptionはスローされません。(Javaのプラットフォーム実装は標準の文字セットをサポートするからです))
本例外をスローするメソッドはRuntimeException等にラップしてスローする事を検討すべきです。

java.lang.Exceptionをスローしない

通常の業務処理において、java.lang.Exceptionをスローすべきではありません。
java.lang.Exceptionを投げてしまうと、エラー(例外)の意味がかなりぼやけてしまいます。対象の処理やレイヤーに適応した名前の付いたExceptionをスローすべきです。
自分の作成したメソッドがjava.lang.Exceptionをスローしている場合、いったいどんな場合にその例外がスローされ、どういう意味を上位に通知しようとしているのか考え直してみるべきです。
(フレームワークやプラットフォーム的な特殊な処理においてはjava.lang.Exceptionを敢てスローするケースがあります)

java.lang.Exceptionをキャッチしない

通常の業務処理において、java.lang.Exceptionをキャッチするのは最小限にすべきです。
汎用的に例外を捕捉できるため、java.lang.Exceptionをキャッチしたくなりますが、java.lang.Exceptionをキャッチするということは、RuntimeExceptionのサブクラスもキャッチするということです。
通常の業務処理でそのようなことを意図する場面はかなり少ないはずです。
RuntimeExceptionのJavadocを見ると分かりますがJava標準でRuntimeExceptionの既知のサブクラスはかなりの種類があります。これらを敢て捕捉したくて、"catch (Exception e)“と書いていますか?
(フレームワークやプラットフォーム、呼び出し階層の上位レイヤー、外部システムとの連携を担うレイヤー等においては、java.lang.Exceptionを敢てキャッチするする役割のクラスが必要な場合もあります)

例外を握りつぶさない

呼び出したメソッドから通知された例外を握りつぶす以下のようなコード書くのは避けた方が良いです。

try{
   someMethod(); // IOExceptionをスローする可能性がある
} catch(Exception e) {
    // 何も書かないで無視する(やってはいけない!!)
}

実際に例外が発生した場合にそのエラー内容や原因が全く分からなくなってしまいエラーの解析などができなくなってしまいます。
基本的には例外に対応した何らかの処理、そのまま(またはラップして)上位に例外をスロー、最悪でも例外内容をログ出力して例外を記録した方が良いです。
以下のコードの場合はjava.lang.Exceptionをcatchして無視しているので、バグなどで発生したRuntimeException(のサブクラス)等も握りつぶしてしまい、
アプリケーションとして挙動がおかしくなってしまう可能性すらあります。
(ただし、close処理などで意図的に例外を無視する場合などの理由があるケースは除きます)

バグを意味するRuntimeExceptionをcatchしない

java標準でRuntimExceptionのサブクラスにはコードのバグによって発生するような例外(IllegalArgumentExceptionIndexOutOfBoundsException・・・etc)が存在します。
そのような例外を通常の業務処理においてキャッチして処理を続行すべきではありません。
例えば、IndexOutOfBoundsExceptionが発生するケースは、C言語ではセグメンテーション違反が発生してまう状況に近いとも言えます。
セグメンテーション違反が発生したら対象アプリケーションは続行できません。
このように考えると、バグを意味する例外を無理やり捕捉して業務処理を続行するのはかなり不自然と言えます。

例外をスローする際はエラーの原因情報を含める

稼働してるアプリケーションで例外が発生してエラーとなった際に、エラー解析を行うためにはログ(に出力されたスタックトレース)が頼りとなります。
エラーを解析する際に頼りとする情報には以下のようなものがあります。

  • 例外(例外クラス)の名前そのもの
    • 例外の名前が具体的であればエラーの原因が推測しやすくなります
  • 例外に含まれている元の原因となった例外のスタックトレース
    • 元となる例外がわかると本当のエラー発生個所が推測しやすくなります
  • エラー内容を意味するメッセージ
    • 例外の内容を補足してくれます。詳細なメッセージが含まれているとエラーの原因が分かりやすくなります。
      エラーの原因情報をできるだけ上位に通知するために、以下の方針で例外をスローした方が良いです
      • 呼び出し元のメソッドで例外が発生し、それをキャッチして新しい例外をスローする場合
        ⇒ 元の例外を新しい例外に含める(元の例外も含めて上位に通知する)
      • 業務的なチェック処理結果からエラー通知する場合
        ⇒ チェック対象の値と何のチェックでNGだったか(何がまずかったのか)をメッセージとして例外に含める
        (※ただしチェック対象の値が巨大なオブジェト(巨大な文字列etc)の場合は含め方に何らかの工夫は必要です)

実行時例外もjavadocに記述する

RuntimeExceptionを継承した例外クラスをスローするメソッドでは、 javadoc の @throwsにその例外を記載しておく必要があります。
ただし、throws節には記述しません。

誰にもcatchされなかった例外のハンドリング

UncaughtExceptionHandlerをThreadに設定することでハンドリングできます

以下はメインスレッドに例外内容をロギングするUncaughtExceptionHandlerを設定するサンプルです。

public class UncaughtExceptionHandlerSample {

    private static final Logger logger = LoggerFactory.getLogger(UncaughtExceptionHandlerSample.class);
 
    private static final UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = new UncaughtExceptionHandler() {
 
        /** 
         * キャッチできなかった場合は、とりあえずログ出力する
         */
        @Override public void uncaughtException(Thread thread, Throwable throwable) {
            logger.error("キャッチできない例外発生", throwable);
        }
 
    };
 
    public static void main(String[] args) {
        // メインスレッドにUncaughtExceptionHandlerを設定
        Thread t = Thread.currentThread();
        t.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER);
    }
}


日時

TimeUnit

java.util.concurrent.TimeUnit(列挙型)を使用すると時間の単位変換などをわかりやすく記述できる。

// 4時間をミリ秒単位に変換
System.out.println(TimeUnit.HOURS.toMillis(4));    // -> 14400000
 
// 1日を秒単位に変換
System.out.println(TimeUnit.DAYS.toSeconds(1));    // -> 86400
 
// 3秒スリープ
TimeUnit.SECONDS.sleep(3);

// 1分スリープ
TimeUnit.MINUTES.sleep(1);

Calendarクラスの月の値範囲

Calendarで扱う月の値範囲は0~11です。(1~12ではありません)
(よく知られた話ですが、分かりにくさがつきまとう。利用時は注意が必要。)

Calendarのメモリ消費量

java.util.Calendarのインスタンスのメモリ消費量は700byteを超えており、普通のクラスに比べてかなりサイズが大きいです。(例えば、java.util.Dateの場合はメモリ消費量 24byte程度です)
そのため、Calendarのインスタンスをクラスのメンバに保持したり、大量に生成したりするのは避けた方が良いです。


JavaBeans

単純なJavaBeansは不整合な可能性あり

単純なgetter/setterを持つJavaBeansはオブジェクトの生成処理が各setterによって分割されているため、生成過程では不整合な状態になっている可能性があるという重大な欠点を持っています。
(特定のメンバには値が入っているが特定のメンバはnullになっていて、そのような状態がクラスとして本当は許容されない等)
クラスを不変(immutable)にすることで基本的には解決します。メンバが多い場合はBuilderパターンを取り入れることでスマートになります。
(※ただし、JavaBeansを必要とする外部ライブラリも存在しますので、そのようなライブラリを使用する場合等はJavaBeansを利用してください)


数値型

double型の正の無限・負の無限・非数

double型には正の無限大値を表すDouble.POSITIVE_INFINITY、負の無限大値を表すDouble.NEGATIVE_INFINITY、非数を表すDouble.NaNが存在します。
通常の処理で有用なのは上記の値を除いた有限値のみです。
外部システムなどの信頼性が確かでないところからdouble値を受け取ったり、文字列からdouble値を作り出す場合などは、上記の値でないことをチェックしておく必要があります。
(非数の挙動として、 Double.NaN == Double.NaN は結果がfalseとなることには注意が必要です)
(正の無限、負の無限、非数についてはfloat型にも同様の事が言えます)

BigDecimalで四捨五入が四捨五入にならない

BigDecimalをdouble型の値を使って生成すると四捨五入処理が場合により五捨六入になってしまいます。
BigDecimalを生成する場合はString型の値を指定するか、BigDecimal.valueOf(double)を使用します。

NG例:

public static BigDecimal roundNumber(double val, int scale) { 
    BigDecimal bd = new BigDecimal(val); 
    bd = bd.setScale(scale, BigDecimal.ROUND_HALF_UP); 
    return bd; 
} 
 
public static void main(String[] args) { 
    System.out.println(roundNumber(9.994, 2).doubleValue()); 
    System.out.println(roundNumber(9.995, 2).doubleValue()); 
    System.out.println(roundNumber(9.996, 2).doubleValue()); 
} 

とすると9.995の四捨五入の結果が10.0ではなく9.99になってしまいます。


防御的プログラミング

防御的コピー

仮にコンストラクタでjava.util.Date型の値を受け取りfinalなメンバとして保持しても、外部から値を変更できてしまいます。
内部的にはDate#getTimeの値を保持しておき必要に応じてDate型に変更するほうが安全です。
このように可変な要素を持つものを保持する場合は防御的にコピーする必要があります。
ただしコピーすることが大きなコストであったり、利用者が同一パッケージで信頼がおける場合はこの限りではありません。
そのような場合は、できればコメントに理由があって防御的コピーを行っていない旨を記載しておくべきです。


java.util.Dateで外部とやり取りしているクラスの防御有無の各パターンのサンプルコード

●防御的コピーをしていないパターン(コンストラクタもgetterも内部表現を暴露しており、外側からDateHolderの内部表現の日時を変更できてしまう)

public final class DateHolder {
        
    /** 日時 */
    private final Date date;
        
    public DateHolder(Date date) {
        if (date == null) {
            throw new NullPointerException("parameter date is null.");
        }
        this.date = date;
    }
        
    public Date getDate() {
        return date;
    }
}


●防御的コピーをしているパターン(外側からDateHolderの内部表現の日時を変更できない)

public final class DateHolder {
    
    /** 日時(ミリ秒表現) */
    private final long dateMillisec;
    
    public DateHolder(Date date) {
        if (date == null) {
            throw new NullPointerException("parameter date is null.");
        }
        this.dateMillisec = date.getTime();
    }
    
    public Date getDate() {
        return new Date(dateMillisec);
    }
}


■String配列で外部とやり取りしているクラスの防御有無の各パターンのサンプルコード

●防御的コピーをしていないパターン(コンストラクタもgetterも内部表現を暴露しており、外側からStringArrayHolderの内部表現の文字列配列の各要素の値を変更できてしまう)

public final class StringArrayHolder {
    
    /** 文字列の配列 */
    private final String[] strArray;
    
    public StringArrayHolder(String[] strArray) {
        if (strArray == null) {
            throw new NullPointerException("parameter strArray is null.");
        }
        this.strArray = strArray;
    }
    
    public String[] getStrArray() {
        return strArray;
    }
}


●防御的コピーをしているパターン(外側からStringArrayHolderの内部表現の文字列配列の各要素を変更できない)

public final class StringArrayHolder {
    
    /** 文字列の配列 */
    private final String[] strArray;
    
    public StringArrayHolder(String[] strArray) {
        if (strArray == null) {
            throw new NullPointerException("parameter strArray is null.");
        }
        this.strArray = strArray.clone();
    }
    
    public String[] getStrArray() {
        return strArray.clone();
    }
}


名前付け

型を誤解させるような変数名をつけない

以下のような変数宣言があった場合に、

private Map<String, String> addressArray;

この変数を使用しているメソッド等で、"addressArray"という変数名だけ見た場合に対象変数の型は、何かの配列型、ArrayList、独自型あたりだと推測してしまいます。
普通はMap型だとは思わないでしょう。
このように型を誤解させるような変数名はつけないようにした方が良いです。型を誤解させるとコードの可読性が下がります。


配列

配列に意味を持たせない

以下のような(各要素ごとに)意味を持たせた配列を使用すべきではありません。対象の配列を使用しているコードの意味が分かりにくくなりバグ混入の可能性が高まります。
配列の代わりに独自のオブジェクト(クラス)を定義すべきです。

// 氏名と郵便番号、住所を格納する配列
String[] personData = new String[3];
personData[0] = "Taro Yamamoto";    // 氏名
personData[1] = "999-1234";         // 郵便番号
personData[2] = "埼玉県";           // 住所
return personData;

上記の配列の場合、要素数や要素の順番が変わってしまうとそれだけでデータとして不整合な状態となってしまいます。
外部とのインターフェースの都合上どうしても意味を持たせた配列が必要な場面がありますが、そのような箇所は局所的にすべきです。
自システム内に意味を持たせた配列を持ち込みすぎるとコードの可読性が圧倒的に下がります。


コメント

外部に公開するAPIにはJavadocを記述する

外部(別システム)に公開する等で作成者以外が使用することが考えられるAPIについては基本的にjavadocを記載した方が良いです。
対象のクラスがスレッドセーフか、スレッドセーフではないのかを記述してあると利用者側にとってはさらに良いです。
(可能ならば全ての変数、メソッドにjavadocコメントが記述してあるとより良いです)

コードをコメントアウトして残さない

ソースコード内に時々コメントアウトされた処理が存在しますが。コメントアウトを行った人間以外から見て、そのコメントアウト部分のコードはノイズにしかなりません。
基本的に不要となった処理はコード上に残さず、即時に削除すべきです。(過去に戻りたい場合はバージョン管理システムの出番です!)
どうしても何らかの理由があって処理コードをコメントアウトして残す場合は、どういう理由でコメントアウトしてあえて残しているのかを強くコメントに記述(補足)しておくべきです。
ただし、残しておくべき理由がなくなったらコメントアウトしたコードは削除すべきです。
(本件はJavaに限らずバージョン管理されている全プログラムにおいて言えることです)


JUnit

テストし易いコードを書く

  • ユニットテストの対象クラスやメソッドが、テストしにくければ、当然ですがJUnitでテストケースを書くのは困難になります。
    テストし易いコードを書くように心掛けた方が良いです。
    テストしにくいということは、実際に(対象のメソッドやクラスを)利用する側から見ても扱いにくいメソッド(やクラス)であると言えます
  • テストし易いメソッドとは?
    • 引数を取る
    • 引数の数が少ない
    • メソッド内の処理中に引数を変更しない
    • 戻り値がある
    • 同一の引数に対して常に同一の戻り値を返す
    • そのメソッドを提供するクラスのインスタンス化がし易い
    • 外部環境(外部システム)に依存していない

Mockライブラリ

ユニットテストを書く際に、Mock化したインスタンスを生成するライブラリを使用すると、ユニットテストケース作成の難易度が下がる場合もあります。
代表的なMockライブラリは以下になります。

  • Mockito
  • JMockit


適切なAPIを使用する

実行効率の悪いコンストラクタ

以下に示すコンストラクタは非効率であるため使用すべきではありません
代替案を使用すべきです。

  • new String()
    • 代替案: “"(空文字列)を使用すべきです
  • new String(String)
    • 代替案: 引数にとっている文字列をそのまま使用すべきです
  • new Integer(int)
    • 代替案: Integer.valueOf(int) を使用すべきです(その他のプリミティブ型でも同様)
  • new Boolean(…)
    • 代替案: Boolean.valueOf(…) を使用すべきです


初期化

staticイニシャライザに複雑な処理を書かない

staticイニシャライザ(静的初期化子)でネストが深くステップ数の多い処理を行うのはNGです。
staticイニシャライザはメソッド等とは違い引数も戻り値もないため、複雑なロジックを書いてもユニットテストでの確認が困難となります。
テスト容易性を確保する上でもstaticイニシャライザに複雑な処理を記述すべきではありません。

インスタンスイニシャライザを使用しない

インスタンスイニシャライザは存在があまり知られていないインスタンスの初期化方法のため基本的に使用すべきではありません。
インスタンスの初期化処理は、コンストラクタに記述すべきです。インスタンスイニシャライザを使用するとコードの可読性が落ちてしまいます。
(ただし、無名クラスにおける初期化処理においては有効に利用できます。が、使用するとしてもその用途に限るべきです。)


クラス構成

ユーティリティクラス

Javaでユーティリティクラスの様なstaticメソッドのみを持つクラスのソースコードの典型例は以下の通りです。

public final class SampleUtils {

    public static void func1() {
        // do something
    }
    
    public static void func2() {
        // do something
    }

    /**
     * インスタンス生成の抑制
     */
    private SampleUtils() {
    }
}

上記コードのポイントは以下2つです。

  • クラスをfinalにして継承による拡張を防ぐ(staticなクラスなので継承は想定されない)
  • コンストラクタをprivateにしてインスタンス化を抑制する(インスタンス化する意味が無いため)

実装クラスは基本的にfinalに

abstractでない(継承されることを想定しない)実装クラスには基本的にfinal修飾子を付与して下さい。
特に他システム等の外部と連携するために公開している実装クラスをfinalにしていない場合、思いもよらない継承をされてしまう場合があります。
想定外のサブクラスが存在してしまうと継承元のクラスが簡単に修正できなくなってしまいます。

パッケージプライベートによるクラスの隠蔽

以下のように実装クラスの可視性をパッケージプライベートにすることで、外部に対して実装を隠蔽することができます。
(※本内容はどんな場面でも使用できるわけではないですがJavaの道具立ての一つとして理解しておくことで、クラス構成・パッケージ構成を考える際の役に立つと思います)

外部に公開しているプロジェクトのパッケージ及びクラス構成は以下の通りとする。
(org.sample.somepackage.apiパッケージに3つファイルがあるとする)

org.sample
└─somepackage
    └─api
         SomethingInterface.java
         SomethingImpl.java
         SomethingFactory.java

各ファイルの概要及び可視性は以下の通り

ファイル 可視性 概要
SomethingInterface.java public 外部が使用することを想定したインターフェース
SomethingImpl.java package private SomethingInterfaceの実装クラス
SomethingFactory.java public SomethingInterfaceの実装を生成して返却するファクトリー

この構成の主な利点は以下2つです。

  • ①外部(他システム、他パッケージ)からインターフェースに対する実装が隠蔽される
    • 外部からSomethingInterfaceを使用したい場合、SomethingFactoryを経由してしか(実装が)取得できない
    • 外部からはSomethingImplの存在が全く見えない
  • ②実装クラスのユニットテストはできる
    • SomethingImplは外部からは操作できないが同一パッケージからは見えるので、同一パッケージ上に配置するテストケースクラスからは直接操作(new)してテストすることができる。

Immutableなクラスにすることを考える

Immutableなクラスはマルチスレッド環境でも同期処理等無しで正常に動作します。クラスをImmutableで設計することは重要です。
Immutableなクラスの例は以下の通りです。
メンバがfinalであるため、コンストラクタ実行後に変更することができません。一般的なJavaBeansと違ってsetterを持ちません。

public final class ImmutableObject {

    private final int id;

    private final String value;

    public ImmutableObject(int id, Sring value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }
    
    public String getValue() {
        return value;
    }
}

メソッド間の値の受け渡し用インスタンス変数を作らない

以下のサンプルコードではexecuteMainLogicメソッドからexecuteLogicDetailをメソッドを呼び出す際の値の受け渡しにインスタンス変数tempVariableを利用しています。
このように、インスタンス変数をメソッドの値の受け渡しのために使用するのはNGです。これを行うと対象クラスはスレッドセーフではなくなります。
下記の例で言えばメソッド間の値の受け渡しには素直に引数を使用すべきです。

public class TempVariableTrial {

    /** executeMainLogic -> executeLogicDetail へ値を渡すための変数 */
    private int tempVariable = 0;
    
    public void executeMainLogic(int value) {
        
        if (value < 0) {
            tempVariable = -1;
        } else {
            tempVariable = 1;
        }
        
        executeLogicDetail();
    }

    private void executeLogicDetail() {
        if (tempVariable > 0) {
            // do something tempVariable positive number
        } else {
            // do something tempVariable zero or negative number
        }
    }
}

Nullオブジェクト

処理中にnullチェック、nullによる分岐が頻発している場合はNullオブジェクトの導入を検討した方が良いです。
処理中にnullを元にした処理が多いとバグが混入しやすくなったり、コードの可読性がおちます

外部に公開するAPI

Javaで外部に公開して利用してもらう手続き(API)の設計方針は以下の通りです。
(実際には外部に公開しないものであっても、以下を意識しておくことは重要です)

  • 手続きの名称が適切で、それを利用したコードの可読性が高いこと。
  • 利用者への要求が可能な限り少ないこと。(引数が少ない、API利用までのセットアップ等が少ない(無い))
  • 基本的には実装でやりとりせずインターフェースで会話させる。(引数、戻り値も可能ならインターフェースにする)
  • 引数、戻り値で受け渡すオブジェクトは極力Immutableにする。(不用意な破壊を防ぐ、スレッドセーフ性の確保)
  • スレッドセーフ性を確保する。(利用側にスレッドセーフにするための同期処理をさせなくて済むようにする)
  • 副作用を起こさない。(手続きの名前から想像できない外部環境へ影響のある処理をしない)
  • 公開する手続きがチェック例外をスローすると利用側にとって負担になることを理解しておく。

staticメソッドだけを持つクラスの注意点

スレッドセーフ性を意識したクラス設計をしていると全くインスタンス変数を持たない(インスタンスメソッドだけを持つ)クラスになる場合があります。
そういった場合に、都度インスタンスを利用側が生成するのも面倒だろうから、メソッドを思いっ切ってstaticメソッドに置き換えてしまっても良いのではないかという考えに陥る場合があります。
しかし、両者には決定的な違いがあることに注意が必要です。
staticメソッドだけにしてしまうとポリモーフィズムを全く利用できなくなるという点です。そうなると必ず実装を利用側に見せる必要がでてきます。
汎用的なユーティリティクラスをstaticメソッドのみのクラスとして実装するのはOKですが、外部に公開するような業務的なAPIをstaticにするのは避けた方が得策です。

クラスはできるだけ小さく

クラスのステップ数が肥大化するとプログラマの手に負えなくなってしまいます。できるだけクラスのステップ数を小さくするように努めるべきです。
ステップ数が肥大化したクラスに対して機能追加などのために変更が必要な場合、変更の影響範囲を的確に検出するための簡単な方法が無いため、
「編集して、そして祈る」というプログラミング手法になってしまいます。
そうなると変更にとても長い時間を要するか、バグが増加するかのどちらかになります。


生成処理

コンストラクタの隠蔽を検討する

実装クラスのコンストラクタを直接呼ばれるとクラス間の結合度が高くなってしまいます。
また、コンストラクタはコード上 “new” としか書かけないので、どういうオブジェクトを生成するのか という意味を持たせるのが難しくなります。
全ての場面においてコンストラクタを見せないようにする必要はありませんが、外部に見せる実装クラスにおいてはコンストラクタを隠蔽する検討をした方がよいでしょう。
例えば、コンストラクタの代わりにstaticファクトリを提供することで、どういうオブジェクトを生成しようとしているかの意味が分かりやすくなったり、
生成処理を隠蔽することで、毎回新しいインスタンスを返すのではなく(キャッシュのように)1つのインスタンスを使いまわして返すことがやり易くなります。


■コンストラクタを公開(public)にして、利用側に毎回コンストラクタを実行してもらうパターン

/**
 * 処理結果のメッセージを表すオブジェクト(コンストラクタ公開パターン)
 */
public final class ResultMsg {

    /** 成功を意味するメッセージか? */
    private final boolean isSuccess;
    
    /** メッセージ文字列 */
    private final String message;

    /**
     * 結果メッセージを生成します。
     * 
     * @param isSuccess 成功を意味するメッセージの場合true, それ以外の場合false
     * @param message メッセージ文字列
     */
    public ResultMsg(boolean isSuccess, String message) {
        this.isSuccess = isSuccess;
        this.message = message;
    }

    /**
     * 成功を意味するメッセージか?を取得します。
     * @return 成功を意味するメッセージか?
     */
    public boolean isSuccess() {
        return isSuccess;
    }

    /**
     * メッセージ文字列を取得します。
     * @return メッセージ文字列
     */
    public String getMsg() {
        return message;
    }
    
    
    /**
     * 対象クラスを実行してみるためのサンプルmain
     * 
     * @param args 実行時の引数
     */
    public static void main(String[] args) {
        // コンストラクタを実行してオブジェクトを生成する
        ResultMsg okMsg = new ResultMsg(true, "The operation was successful.");     // 成功メッセージ
        ResultMsg ngMsg = new ResultMsg(false, "The operation was failed.");        // 失敗メッセージ
        ResultMsg emptyMsg = new ResultMsg(true, "");                               // 空メッセージ
    }
}

■コンストラクタを非公開(private)にして、利用側にstaticファクトリを実行してもらうパターン
(呼び出し側コードにおいて、成功 or 失敗のメッセージを生成していることが分かりやすくなる)

/**
 * 処理結果のメッセージを表すオブジェクト(コンストラクタ非公開パターン)
 */
public final class ResultMsg {
    
    /** 空を意味するメッセージ */
    private static final ResultMsg EMPTY_MSG = ok("");

    /** 成功を意味するメッセージか? */
    private final boolean isSuccess;
    
    /** メッセージ文字列 */
    private final String message;
    
    /**
     * 成功メッセージを生成します。
     * 
     * @param message メッセージ文字列
     * @return 成功メッセージを意味するオブジェクト
     */
    public static ResultMsg ok(String message) {
        return new ResultMsg(true, message);
    }
    
    /**
     * 失敗メッセージを生成します。
     * 
     * @param message メッセージ文字列
     * @return 失敗メッセージを意味するオブジェクト
     */
    public static ResultMsg ng(String message) {
        return new ResultMsg(false, message);
    }

    /**
     * 空を意味するメッセージを返します。
     * 
     * @return 空のメッセージを意味するオブジェクト
     */
    public static ResultMsg empty() {
        return EMPTY_MSG;
    }

    /**
     * 結果メッセージを生成します。
     * 
     * @param isSuccess 成功を意味するメッセージの場合true, それ以外の場合false
     * @param message メッセージ文字列
     */
    private ResultMsg(boolean isSuccess, String message) {        
        this.isSuccess = isSuccess;
        this.message = message;
    }

    /**
     * 成功を意味するメッセージか?を取得します。
     * @return 成功を意味するメッセージか?
     */
    public boolean isSuccess() {
        return isSuccess;
    }

    /**
     * メッセージ文字列を取得します。
     * @return メッセージ文字列
     */
    public String getMsg() {
        return message;
    }
    
    /**
     * 対象クラスを実行してみるためのサンプルmain
     * 
     * @param args 実行時の引数
     */
    public static void main(String[] args) {
        // staticファクトリー経由でオブジェクトを生成する
        ResultMsg okMsg = ResultMsg.ok("The operation was successful.");   // 成功メッセージ
        ResultMsg ngMsg = ResultMsg.ng("The operation was failed.");       // 失敗メッセージ
        ResultMsg emptyMsg = ResultMsg.empty();                            // 空メッセージ
    }
}


ロック

Semaphoreをバイナリセマフォとして利用する際の注意

java.util.concurrent.Semaphoreをバイナリセマフォ(いわゆるmutex)のように使用する場合は注意が必要です。
コンストラクタで指定したpermitsの初期値を1とした場合でも、releaseメソッドを呼び出す度に値が初期値を無視して2, 3, 4と増えます。
そのため、思いもよらないタイミングでtryAcquireが間違って成功してしまう場合があります。
(バイナリセマフォとして利用するには内部のpermitsが必ず0か1でのみ遷移するように外部から制御するなどの処置が必要になります)



関連エントリ

Go言語LT大会! 「最近、Go言語始めました」の会に行ってきた #golangjp

Go言語LT大会! 「最近、Go言語始めました」の会に参加してきました。

以下自分用のメモです。


概要

Go言語入門者のLT大会&懇親会

  • 開催日: 2017/6/5
  • 場所: レバレジーズ株式会社


おススメ・標準・準標準パッケージ20選

上田拓也 氏 (メルカリ/ソウゾウ)

  • Go標準のパッケージ
    • 様々な機能が提供されている
      • HTTPサーバ/クライアント
      • 文字列処理
      • 暗号化
      • 画像処理
    • 160パッケージぐらいある
  • Go標準のパッケージ
    • gokang.org/xで提供されるパッケージ
    • 450パッケージ以上ある
  • fmtパッケージ
    • 書式付きプリント機能を提供する
    • %T, %q %vあたりが便利
    • %[1]d, %[2]dとかでN番目の値が出せる。引数の順番を指定できる。引数を使いまわすこともできる
    • fmt.Stringerが便利
      • JavaのtoString的な役割のインターフェース
  • ioパッケージ
    • IO関係の基本的な型が定義されている
      • io.Readerとio.Writerが強力
        • インターフェースの良い例
      • io.TeeReaderとかもあり便利
        • UNIXのteeコマンド的なもの
  • io/ioutilパッケージ
    • IO関係の便利関数がある
    • NopCloserが便利
    • ReadAllhあんまり使わない
      • ストリームのままやろう
  • stringsパッケージ
    • 文字列に関する処理を提供
    • strings.Readerが便利
      • 文字列をio.Readerに変換してくれる
    • strings.Fieldsが便利
      • ホワイトスペースでいい感じに区切ってくれる
  • bytesパッケージ
    • strringsにあるものはだいたいある
    • bytes.Readerが便利
      • []byteをio.Readerに変換できる
    • bytes.Bufferが便利
      • ゼロ値で扱える
  • bufioパッケージ
    • bufio.Scannerが便利
    • bufio.SplitFuncでカスタマイズ
  • encoding/binaryパッケージ
    • 構造体とバイト列をマッピングできる
    • Go1.9でmath/bitsが入る
  • x/textパッケージ
    • 日本語の文字コードに対応(EUCJO,Shift-JIS,ISO2022JPに対応)
    • 多言語化の機能を提供
    • transform.TransFormerが便利
      • ストリームのまま変換できる
      • 実際の例を何回か読まないと理解が難しい
  • flagパッケージ
    • コマンドライン引数に関する機能を提供
      • オプションパースが便利
      • flag.Argsでオプション以外の引数が取れる
      • ポインタの使用例としてGood
  • path/filepathパッケージ
    • ファイルパスに関係する処理を提供する
      • \や/を意識しなくてよい
    • filepath.Walkが便利
      • 再帰的にディレクトリを読んでいく
  • text/templateパッケージ
    • html/templateはHTML特化版
    • コマンドライン引数に使うと便利
    • コードジェネレーションにも利用できる
  • testingパッケージ
    • go testでユニットテストが実行できる
    • Go1.7からサブテストが可能
    • テストを並列で実行可能
      • t.Paralellを使う
    • カバレッジが取れる
      • -cover
  • net/httpパッケージ
    • HTTPサーバ.\/クライアントを提供する
      • http.Handlerを実装すればよい
      • インターフェース実装の良い例
      • 重量なWebフレームワークは必要ない
      • テストしやすい
  • net/http/httptestパッケージ
    • HTTPサーバのテストで使える機能を提供
      • ハンドラのテストが便利
      • httpdocも便利
  • syncパッケージ
    • ロックなどの機能を提供
      • sync.WaitGroupが便利
      • sync.Onceが便利
        • 1回だけ実行したい場合に楽
  • contextパッケージ
    • 複数のGoルーチンをまたいだキャンセル機能などに使える
    • context.Contextが強力
      • インターフェースなので拡張しやすい
    • errgroupパッケージも便利
  • imageパッケージ
    • 画像処理を提供するパッケージ
      • image.Imageがインターフェース
        • 実装すればなんでも画像として扱える
      • pngやjpeg,gifなどに対応している
  • x/imageパッケージ
    • 標準パッケージでは足りていない機能を提供
    • x/image/drawが描画機能を提供してくれる
    • フォントに関する機能も提供している
  • reflectパッケージ
    • リフレクションの機能を提供する
      • structタグにアクセスする唯一の方法
      • 使いどころを間違えなければ便利。遅いので乱用はNG
      • 食わず嫌いは良くない
      • パニックが起こりなくるのでちゃんとチェックを行うこと
  • goパッケージ
    • 静的解析の機能を提供する
      • 抽象構文木(AST)が取得できる
      • 式単位でもパースできる
      • 型チェックもできる
      • 自作開発ツールも作ることができる


Go開発合宿2017

@n0bisuke 氏

  • Goを書く合宿
    • いろんな人が集まってわいわいやるコミュニティ
  • 合宿場所にした土善旅館は開発合宿プランを用意している
    • 過去の合宿者の要望をもとにした優れた設備等がある


初心者がGoでCLIツールを作ってみて学んだこと

@syossan27 氏

  • 作ったOSS
    • kirimori
    • torisetsu
  • 初心者ならではの様々な失敗
    • packageで分ける粒度が分からず1つのファイルにすべて突っ込む
    • エラーハンドリング時に適当にpanicを使ったために自分がpani
    • 構造体メソッドンに対してポインタレシーバを使わない
    • やたら変数名を長くしてしまう


作って学ぶミニマムgolint

@minamijoyo 氏

  • golint
    • Goっぽくないコーディングスタイルを指摘してくれる
    • どうやって検出しているのか?
      • ただの辞書だった
  • よくわからないのでgolint作ってみた
    • 標準のgoパッケージを使う


j2hの紹介

@kanga333 氏

  • j2hを作った
    • JSONからHiveのDDLを出力してくれる
  • gjson
    • goでJSONを取り扱うライブラリの一つ
    • result.TypeでJSONのタイプを取得できる
    • ForReachで子要素に対して反復処理ができる


GoとLintのおいしい関係

@wata727 氏

GoとLintのおいしい関係 Go言語LT大会

  • Lint
    • コンパイラより詳細かつ厳密なチェック
  • Golint
    • Goのスタイルチェッカー
    • (個人的に)Golintの良いところ:行の長さを制限しない
  • Govet
    • コンパイラよりも詳しい検査を行う
  • Go Meta Linter
    • セキュリティとかtypoのチェックとか
  • goは標準パーサがある
    • 新しいシンタックスにも追従してくれる
  • Linterを作りやすい
  • GoではLintを使おう。Linterを作るならGoでやろう


Golangちゃんと始めてから1カ月経ちました

@nntsugu 氏

  • Datadog
    • デザインファーストなFWのgoaで書く
  • プチ合宿やりませんか?


We “Go” fast

@taison124 氏

  • Goで開発
    • 環境や作法が決まっていて迷いどころない
      • 暗黙知が排除されている
    • 標準のやり方でできる
  • 本質的な問題解決に時間や情報を集中できる

個人的にこのLTが一番ためになりました!!


コネクションプールをGoでどう作ろうか調べた話

Jumpei Mano 氏

  • golibmc
    • 1つのコネクションをロックしながら使いまわしている
    • ボトルネックになる
  • memcached用コネクションプールを実装するためにdatabase/sqlの実装を読んだ
    • 非常にいい勉強になった
    • Go特有のchannelを使うことで実装がすっきりしている


ずっとRubyをやっていたエンジニアがGoに入門して挫折して再挑戦した話

@suzan2go 氏

  • 敗因
    • Go言語でRubyのようなオブジェクト指向をやろうとしてしまった
    • Rubyのように楽に書く方法がないか探し求めてしまった
  • 再挑戦
    • GitHubでGo言語のアプリケーションを色々見てみる
    • Go言語の設計についての記事を色々読んでみる
    • Goに従ってみると楽しくなってきた
  • GoDocが凄い
  • Goに入ればGoに従え



関連エントリ

golang.tokyo #6 に行ってきたよ #golangtokyo

golang.tokyo #6 に参加してきました。

以下自分用のメモです。


概要

  • 開催日: 2017/6/1
  • 場所: 株式会社ディー・エヌ・エー


Gopher Fest 2017 参加レポート

上田拓也 氏 (メルカリ/ソウゾウ)

  • Gopher Festとは?
    • Google I/O に合わせて開催されるイベント
    • 5/15にサンフランシスコで開催
  • The State of Go
    • Goの開発状況
      • Go1.8は2/16にリリース済
      • Go1.9は5月1日にコードフリーズ済
    • 言語仕様の変更
      • 型のエイリアスを定義できるようになる
        • 完全に同じ型
        • キャスト不要
        • エイリアスの方でメソッドは定義できない
    • 標準ライブラリの変更
      • math/bits
        • ビット演算に便利なライブラリ
      • sync.Map
        • スレッドセーフなマップ
      • html/templateがより安全に
      • os.Exec
        • 重複する環境変数を削除するように、一番後ろのものを優先するようになった(直感的になった)
    • ツール類の変更
      • コンパイラのエラーメッセージの改善(少し優しくなった笑)
    • コンパイラの速度改善
    • go testの改善
      • 複数パッケージの場合にvendorを無視
    • go doc
      • フィールドにリンクが貼れるようになった


初めてGolangで大規模Microservicesを作り得た教訓

Yuichi MURATA (DeNA)

  • Golangで大規模なMicroservices構成のAndAppを作ったときに得た教訓
  • AndAppの基本構成/アーキテクチャ
    • Google App Engine / Go 1.6
    • Gin / Echo
  • 教訓1:フレームワークにこだわらない
    • Ginで始めた開発
      • シンプルで使いやすかった
      • App Engineとのインテグレーションもサンプルにあった
      • Framework Context vs App Engine Context 問題
        • gin,ContextからApp Engine Contextを派生させ回避  * Echo
      • ワイルドカードパスを共有したルーティングが可能
      • Webアプリ系のコンポーネント開発が捗る
      • 設計がガリガリ書き換わる
      • Version3でコンテキストの派生が不可能に
    • そもそもGoでフレームワークにこだわらない
      • 洗練されたnet/httpパッケージがある
      • Golangは言語仕様がシンプルかつgofmtがある
  • 教訓2: interfaceを尊重する
    • 独自のエラー型を定義して利用することに
    • Nilには型があるの罠
      • errorインターフェースと独自エラー型を混在した時に発生
    • 素直に他の関数にならってerrorインt-フェースに統一すればよかった
    • interfaceの定義されたものを積極的に使った方が標準ライブラリとの連携がシンプルになる
  • 教訓3: regex compile / reflection は遅い
    • 複数スキーマを読み込むためにgojsonchemaを選択
    • パフォーマンステストで問題が発覚
      • バリデーションの有無でパフォーマンスに顕著な差が出ることがわかった
      • パースの度にRegexコンパイルしていた
      • 動的にスキーマを解析する部分でrefletionを多用していた
    • そもそもgo-jsvalで頑張ればさらに段違いに良い結果になった・・
    • regex / reflection などのコスト高の操作を意識する
  • 教訓4: 非対称暗号は遅い
    • Microservicesにおいて肝となる認証・認可
      • JSON Web Token / Json Web Signatureを活用している
    • 非対称鍵の署名が重い
      • opensslと比べgoのcrypto/rsaが貧弱
      • 対象会議の署名:0.37msec, 非対称鍵の署名:486msec
    • cgoを使ったopensslのバインディングも存在する
  • まとめ
    • 困ったときにGoの哲学に帰りシンプルなアプローチをとる
    • Goを過信せずにパフォーマンスに気を配る


ゲーム開発には欠かせない?!あれをシュッと見る

Ryosuke Yabuki (カヤック)

  • CSV
    • CSVは視認性が悪い
    • カラムとデータの関係性が見づらい
  • csviewer
    • CSVをいい感じに表示するコマンドラインツール
    • CSVをテーブル形式で表示してくれる
    • 絞り込みなどもできる
  • 実装で使った便利だったライブラリ
    • sliceflag
      • 複数の値を肝がんに受け取ることができる
      • flagの普段の使い方と大きく変わらない
    • tablewriter]
      • CSVを表示するだけなら相当少ないコード量で済む


Go Code Review Comment を翻訳した話

鎌田健史 氏 (KLab)

  • Go Code Review Commentsとは
    • コードレビューする際にます見るべき箇所をまとめたもの
    • Effective Goを簡単にしたもの
  • 幾つかのカテゴリに分かれている
    • コードの見た目を改善
      • golintめっちゃ優秀
    • コメントや文章の体裁
    • tips系
    • 設計の指針になるようなもの
      • レシーバをポインタにするかどうか迷ったら読もう


ScalaからGo

James (エウレカ)

  • Scala
    • 経験者は関数型っぽく書く
  • 関数型開発はGoでできますか?

  • No!
  • 関数型開発のコンセプトはGoで使える?
    • Yes!
  • 関数型開発とは?
    • 副作用がない開発
  • コードレベル
    • 部分適用はGoでもできる
  • ScalaとGoどっちが好き?
    • Scalaが好き
    • 企業としてはGoがいいかも。Goの方が初心者でも綺麗なコードが書ける


Crypto in Go

Kengo Suzuki (マネーフォワード)

  • Goにおける暗号アルゴリズムを利用する
    • ECBモード
    • パディングとHMACの実装面倒くさい
    • ASE + GCM
      • とてもシンプル



関連エントリ・リンク