Goのアンチパターン


Go書いててなんとなく見えてきた Goでやっちゃいけないパターン

WAF導入してらくらくWebアプリ

  • WAF自体が現在群雄割拠状態。
  • WAF毎にハンドラインターフェースが違うので既存コードつなぐにはラッパーが必要。
  • どのWAFもLL言語に比べるとまだまだフィーチャーの網羅範囲が狭い。
  • なのでもちろんLL言語ほど楽には書けないことが多い。
  • リフレクション使いまくりでトータル性能はLL言語並みに遅いのもある。
  • Go1.7のcontextパッケージの導入で標準のHTTPハンドラが復権する可能性があり更に荒れる予想。

追記:

楽できるのを期待してWAFを導入するの「やっちゃいけない」とまでは言い過ぎだったかもしれないけれど例のsqlでPrepareを正しく使えていないで性能出なかった件とか、当面WAFを使うなら自分で概ね中身を理解して使う覚悟が必要。

構造体メソッドにロジックを詰め込む

  • Goの思想は「クラスの継承は不要」
  • 継承の真似事をしても基本は継承元型経由で呼ばれたメソッドの振る舞いを変えることはできない。
  • メソッドをすべて再実装すれば変えられるがその時点で元の構造体はデータの入れ物に過ぎない。
  • あくまでロジックや手続きは関数として提供するほうが使い勝手が良い。
  • メソッドは引数展開して関数を呼ぶスタブとして定義するくらいがちょうどいい。
  • ビッグクラス指向で非同期処理書き始めるとあっという間にスパゲティ化する。

sync.Mutex使いまくり

  • Goの思想は「並列処理の協調にはメモリの共有じゃなく通信を使え」
  • sync.Mutex=排他ロック機構でメモリの共有をしてそのアクセスを直列化するために使う。
  • 排他ロックを濫用すると並列処理を頻繁にブロックしてgoroutineがスケールしなくなる。
  • マイクロベンチではchanを使う実装より使わない実装が高速な結果が得られやすいが鵜呑みにしてはいけない。
  • chan単体の動作コストはそれなりにあるが並列処理の妨げが少ないのでスケールしやすい。

json/xmlを型無しでパースしたい

  • Goの思想は「外部インターフェース定義で手を抜くな」
  • Goでは一応できる仕掛けはあるが値にバインドするの超面倒。
  • 定義がちゃんとが無いのは将来バグの温床になる。
  • 型が不定である場合はなおさら。
  • 結局は受取用の構造体をちゃんと定義して使うのがベスト。
  • 使い捨てで処理実装したいならLL言語使おう。

エラー処理を楽に書く

  • Goの思想は「エラーハンドリングで手を抜くな。」
  • Goでは必要があれば返値としてerrorオブジェクトを返し、受け取った側は愚直にチェックするのみ。
  • 例外機構はエラーハンドリングの手抜きの温床になりやすい。
  • また、非同期の世界では独立したスタックが乱立するので旧来の例外機構が使えない。
  • (実際非同期JSライブラリの利用時はtry…catchは使われなくなってる)
  • モダンな言語ではasync-awaitで非同期の世界に例外機構を持ち込もうとしてるが、
  • 裏では通信を使う実装が隠されていることに起因した細かい制約も多い。
  • Goでは必要があればchanを明示的に使ってシンプルにエラー通知を実装すればよい。

フィーチャーリッチなロガーを導入する

  • Goの思想は「それホントに要るのかよく考えろ」
  • 軽量で大量に処理する実装ではフィーチャーリッチなロガーがボトルネックになる。
  • 並列処理の妨げ要因のトップがロガー等に代表されるディスク書き込み。
  • Warningなどの細かいレベル分けはユーザーにとって何も意味はない。
  • 無視するか気にしすぎて余計なサポートが必要になるだけ。
  • ログに必要なのは情報かエラーか2択で十分。
  • 目的が違いすぎて情報とエラーを同じファイルに出力するのもたいして意味がない。
  • そして標準のロガーは程よくチューンされていて他のフィーチャーリッチなロガーに比べてかなり早い。
  • 結論は標準のロガー+追加のロガー1インスタンス(エラーログと動作ログ)でほぼ事足りる。

nilの暗黙の型変換

  • Goは唯一の例外以外は必ず明示しなくてはエラーになるほど型厳格。
  • 非interface型からinterface型への型変換だけは暗黙に変換できちゃう。
  • この時nilを型変換しちゃうと型付nilになっちゃうのでnilチェックしづらくなる。
  • 必ずこの型変換の前にnilチェックすること( Go言語の鉄則 )。