HTML
DB
設計
マイクロサービス
プログラミング作法

やめた方が良いコーディング・設計、アンチパターン


目的

レビュー時にアーとなる、お話にならない設計をまとめていきたい。

自分のためというより、話が通じないレベルの設計を言語化したい。

プログラミング言語関係なく、WEBサービスを作る時の、コーディング・設計の基本アンチパターン集としてまとめたい。

こんなコーディングや設計をする人がいないと思うレベルの悪質度の高いもの、極力避けてほしい書き方・設計を、気づいたらここに記述していく。

前半は「リーダブルコード」を読めば十分に分かる話だと思うし、殆ど超当たり前のことしか書いてないつもりなので、HTML~リリースの章を読んでほしい。


コード憎んで人を憎まず、罪を憎んで人を憎まず

「コード憎んで人を憎まず」というが、まずはそれが罪だと感じてる人がいる事が分からないと話は進まない。出来るならレビューの前に、ソースコードを書く前に、解決しておきたい問題が殆どである。

世の中には二種類の人間がいる。クソコード耐性が高い人と低い人だ。「クソコード耐性が高い人」はクソコードを生成しやすい。なぜならそれをクソコードと思わないからである。

自分のように「クソコード耐性が低い人」がどういうコードを問題視して罪だと思うのか。レビュー時に嫌な思いになるのかを「クソコード耐性が高い人」は察して改善してほしい。それが叶うなら徐々にレビューは互いに幸せに行えるだろう。

新人が気づかずに書いてしまうことはよくあるしそれ自体は問題ではない。指摘すればいいだけの話だし教えればいいだけの話である。次から書かなくなればそれでいい。

だが、一番厄介なのは罪を罪と認識できず何年も罪を量産している「クソコード耐性の高いベテランプログラマ」である。こういうケースでは、大人の事情や納期などの理由で、レビュー時点ではすでに手遅れになる事は多い。

CIやlintを設定しても、開発の邪魔なので閾値をあげて実質、無効化してしまうベテランプログラマが多数いる。

建設的な設計議論は、最低限の罪を排除してから始めていきたい。その上で妥協するところは妥協すればいいと思う。全ての罪でも例外すべきパターン・設計はたくさん存在する。だがそれを初めから問題のあるコーディングだと思わないのが問題である。

こういうコーディング法や設計方法もあるという議論はソースコード作成中に、開発中にするのが望ましいし、レビュー地点でやるべき論議ではない。レビュー時に発生する地点で何かが足りないのだと思う。

なので先に「罪」だと思うパターンを洗い出しておきたい。それに対してどう対策を取るかはそれぞれのシステムで変わるので勝手にすればいいし、自分の中では「オーソドックスに最もよく使われている設計」でほぼ置き換えることが出来ると思う。

なぜ「罪」かが分からなければ勉強するしか無いと思う。そんな詳細は自分が書くよりググって本を読んだほうが良い。良い参考文献があったら教えて下さい。

これ罪じゃなくね?というのがあれば、議論すればいいと思う。時代の変化でコードが進化し状況が変わり、(JS界隈は特に)罪にならなくなるものもあったが、あえて記述を残した。


超基本、マナー


(追記)自分で書いたコードを読まない

自分で書いたコードを人に依頼する前に自分で読まない人がいる。何を書いてるのか自分でも分からない人がいる。

せめてPRを依頼したタイミングで、もしくは依頼する前に、GitHubでPRを作ったタイミングで、自分のコードをブラウザ上で全部読みなそう。そして自分のコードをレビューしましょう。きっとそこには想定外のクソコードやバグがあるかもしれません。

自分で書いたクソコードに自分で気づき、先に直す、これはとても大切な行為であり大事なマナーである。


インデントを揃えない

言語によって異なるので割愛するが、最低限インデントはきちんと整えましょう。

エディタやIDEの自動整形を使ってほしい。もしくは、コミット時に自動整形するツールを導入したい。

ScalaならScalafmtを使えばいいし、Golangならgo fmtgoimports がある。各種言語でたくさんあるので好きなものを使えばいいと思う。

IntelliJならコードフォーマッタを使えばいいし、保存時にフォーマットが実行されるようにすればいい。

https://qiita.com/sisidovski/items/bde2d844c3c73457923c

チームとしてなにか共通した仕組みがあるのが理想的である。


ファイルの末尾が改行コード \n ではない

言語によってはファイルの最後に改行がないと正しく機能しないこともありえる。

PHPのPSR-2などコーディングガイドで規約がされている言語も多い。Gitでも「no newline at end of file」が出ることもあり、C言語でも「warning: no newline at end of file」という警告がコンパイラから出る。

GitHubで毎回ファイル末尾に改行無し表記が表示されるのがウザいのでやめてほしい。

vimもemacsもIntelliJもエディタで自動で設定できるので、マナーとしてファイルの最後で空行を入れましょう。もしくは自動整形ツールを使うのが良い。

https://kamatama41.hatenablog.com/entry/2015/09/10/194414


条件分岐が多い、階層が多い、循環的複雑度が高い

メソッド自体の責務が多い、無意味な分岐が多い場合に、循環的複雑度が跳ね上がることが多い。

分岐が多い、循環的複雑度が多いということは、考慮しなければいけない条件が多いわけで、当然バグを埋め込みやすくなる。罪である。

典型的なif文の分岐、ネストに関しては下記ページが詳しく書かれているため、一読する事をオススメする。

https://qiita.com/pakkun/items/9bef9132f168ba0befd7

深いネストはlintで対策すべきだとは思うが、私の経験上、循環的複雑度が50を超えると読めなくなるしバグが増える。100を超えると仕様自体も分からなくなり、1からの作り直しも難しくなる。最高で700超えも見たことがある。

lintで計測した結果をちゃんと対策できるチーム作りが重要である。lintにかかる閾値をあげられて回避されては意味がない。循環的複雑度が高い事は罪である。


不要な再帰処理

※ Haskel、Erlangなどの関数型プログラミング言語は除く。(関数型の書き方はループの代わりに再起を使うので不要ではなく必要不可欠です。)

再帰処理が必要なほど数学ロジックが求められるようなアルゴリズムや、階乗の計算や、ハノイの塔を解くアルゴリズムや、ソート処理、再起をしなければループ回数が不明な処理でも作っていなければ、99%はその再帰処理は普通のコーディングで十分表現できる。

基本的なWEBサービスの構築に再帰処理が必要な理由が理解できない。というか、ほぼいらないと思っている。

再帰処理は可読性が大きく下がり、他人が書いた再帰処理は理解の難易度が上がる。 Stack Overflow の問題も発生しやすく、今は大丈夫でも将来第三者が引き金を引いてしまうことを考慮すべきである。百行以上の巨大な再帰処理は理解の難易度からバグがとても埋め込まれやすい。

個人経験としては過去に見た約千行の再帰処理は3人で挑んでやっと読み解けるかギリギリのレベルで、追加改修は断念した。

パフォーマンスの最適化・チューニング、特殊データ構造など、特化した理由が無ければ再帰処理は避けるべきであろう。

不要な再帰処理は罪である。


共通化・抽象化


なにも考えずにコードを共通化せず、コピペする

※時には影響範囲を小さくするために、あえて共通化・抽象化を避けるパターンもあるだろう。そういう考えられた設計は除外する。

同じような処理が別に必要になったときに、共通化、module化、カプセル化などの修正作業を行わず、なにも考えずに単純にソースをコピペする人が世の中には存在する。

最悪のケースでは、ファイルごとコピー、ディレクトリごとコピーされる。

大多数の人は出会うまではありえないと思うだろうがこれが本当にいるのだ。マジキチである。

保守する人の気持ち、後に改修する人の気持ちを考えてほしい。「リリースを早くするためにコーディングのスピードを上げる」などという言い訳は通用しない。

その愚行により、その後にその機能の追加改修コストが膨れ上がるのだ。それをイメージしてほしい。想像してほしい。今後なにが起きるかを考えてほしい。第三者が改修するときに困り、苦しむことを想像してほしい。あなたのその行為は愚行であり、システムに毒を埋め込む行為だという事を自覚してほしい。大罪を超えて終身刑レベルである。


module化、カプセル化しない、意味のあるメソッド切り分けをしないスパゲッティコード

ちゃんと仕様ごとに意味のあるClass分け、module分けをしてほしい。

数百行のメソッドを作らないで欲しい。あとで読む人のことを、あとで修正する人のことを考えてほしい。

第三者が読むときにどう理解するのか、第三者が拡張するときにどう修正するのかを想像してほしい。罪である。

じゃあどうすればいいのかという話だが、シンプルにオブジェクト指向を貫くのであれば「単一責任の原則」は最低限守ってほしい。

他のオブジェクト指向設計原則も守るだけで、かなり可読性の高いコード、保守性の高いコードになるでしょう。

https://qiita.com/UWControl/items/98671f53120ae47ff93a


抽象化を全くしない、もしくは極端に抽象化しすぎる

ケースバイケースなので全パターンに当てはまらないが、抽象化を全くしないということは普通のコーディングではありえない。

必ずシステムを開発中には何かしらの共通化・抽象化が発生するものであり、そこを回避することはまず不可能である。抽象化すべきところは抽象化しましょう。

逆に抽象化を極端にしすぎるケースも多々あるが、コストパフォーマンスの問題と可読性の問題、影響範囲が広がる問題が存在する。

極端な抽象化と共通化は、修正時の影響範囲の拡大も伴い、修正コストや確認コストが跳ね上がるだろう。あまりに巨大な抽象化・共通化はバージョニングの概念も設けたほうが良い。(たとえば10のレポジトリから、抽象化されたライブラリをsubmodule化して使うなど)

どちらも難しい問題であるが、「なにも考えない」事は罪である。ましてやコピペは決して許されない。



IntやBoolなどを使わず、Stringで全て表現しようとする

プログラミングというものを一から勉強し直したほうが良い。

それが数字なのか、それがtrue/falseの2つから表現されるものなのか というのは非常に重要な情報で、バグの回避としてプログラマにもコンパイラにも非常に有効で有用な情報である。

それをあえて回避するのが全く理解が出来ない。

型で表現できるものは型を使いましょう。なぜ型という概念があるのかを理解してほしい。いうまでもなく罪である。


連想配列やMapを使い、String -> String で全てを表現しようとする

意図しないバグが起きてもコンパイルでエラー検知が出来ない、何が保存されているのか可視化されないのが罪。

第三者が見たときに、リファクタしようとしたときに、仕様を追加しようとしたときに影響範囲が不明となりやすい。

コンパイル言語であれば、普通に変数で定義すれば良い。バグがあったらコンパイルエラーで気づける。変数であれば型の情報も使える。なんでもかんでもStringでは全てわからない。

コンパイル言語でClassやStructを使わず全てを連想配列やMapで表現する意義が一体何があるのか?デメリットしか無い。罪である。


(Go)interface{}、(Scala)型パラメータ、(Java)ジェネリクス などの型不定が可能な機能を乱用する

※この3つが並んでることにおやっ?と思う方は分かってる人だと思うのでスルーして下さい。

型が固定化されてることで安全が保たれている。正しく型付けされているという事は想定外の不正な動作をしない事が保証されている。そしてそれを崩すということは、不要なバグを埋め込みやすくなる危険度が上がる。

それらをわかった上で抽象化のためにこれらを使うのは全く問題ないし、抽象化を進めるためにはむしろ使ったほうが良い。

ただ、例えばGolangでinterface{}を型情報をわざと消すために使い楽をする、など本来の言語設計意図ではない乱用化をするのは罪である。そこに差異がある。


(PHP) タイプヒンティング、強い型付けを使わない

メソッドの引数に変なデータを突っ込まれないという最大防御のスーパーアルティメッド技がPHPに導入されたのに、なぜそれを使わないのか。先生悲しいです。

変数の型が再代入で変化するという特徴を持つPHPにおいて、それがintなのかstringなのかarrayなのかが分かるというのはPHP開発者に取っては非常にありがたく、不要なバグを埋め込まない。

とくにPHPでは、10+"20"=30 のように自動型変換が効いてしまうので意図しない型変換バグを埋め込みやすいし、意図しない NULL が入ってることもある。

使えるところはタイプヒンティング・型付けを定義してほしい。使わないのは罪である。

追記:PHP7から強い型付けが定義できます。

https://qiita.com/bokotomo/items/1ead446ef689dc45b487


(JSなど)厳密等価演算子 === を使わない

変数の比較時に、"1"==1 -> true になるような言語のときは、厳密等価演算子 === を使ってほしい。

等価演算子 == を使わないでほしい。理由は型による比較ができず、不要なバグを埋め込みやすくなるからである。

その時は大丈夫でも、将来その機能が第三者に拡張されてバグを埋め込まれる可能性を想像してほしい。厳密等価演算子を使わないのは罪である。

JSLint, ESLint、などなど各種lintを入れて検出・自動修正しましょう。


変数


同じ意味、同じIDを指す2つ以上の変数名をシステム内に作る

同じ意味を指すものは常にシステム間で1つの名前で表現してほしい。

例えば、 videoIdvidsmallId なるものが全部同じIDを示していたことがあった。

他の例だと、 userIdaccountIdstaffId と sessionIdemployeeId と masterId が全部同一IDを指してる事もあった。

それ1つでよくない? なぜ2種類以上の表現を使うのか?

第三者が見たときにそれが違うものを指すと思う事を想像してほしい。無意味な混乱を与えることに気づいてほしい。罪である。

余談ですが、ドメイン駆動設計ではユビキタス言語という共通言語の概念があり、適切な名前付けができれば自然と変数名はシステム内で一意になります。

https://qiita.com/kmdsbng/items/bf415afbeec239a7fd63


変数のスコープを考えない、不必要に広くする

不必要なグローバル変数を使うのは禁止。他の変数も極力スコープは最小限にしてほしい。

変数のスコープを狭くすることは、後の改修やリファクタで非常に有効であるし、コードの可読性も非常に良くなる。

ゆえに不必要にスコープを広くする事、スコープを考えないことは罪である。


変数に再代入する

定数で良いものは定数を使う。一度定義した変数に代入する行為は可読性を下げ、バグを生みやすくする。難しい言葉を使うならば、参照透過性を壊すのだ。

ましてや、PHPのように型も変えれるものは深刻である。さっきまで0だと思っていたものが"0"の文字列になっていた時の衝撃は計り知れないし、不幸な実行時エラーを招く。

大多数の変数は、(例えばJavaScriptであればconstなどの)定数で定義でき、再代入をしなくても良い。状態変化をすることは最小限にすべきであり、不要な状態変化は罪である。

https://qiita.com/jwhaco/items/6851a0a88a35c5a54fc0


(Scala) 暗黙の型変換 implicit def を乱用する

発狂するのでやめて。現代ではもう使わないことが推奨されています。

普通に書いて下さい。普通に関数を呼んで下さい。暗黙の型変換を使うのはやめて下さい。


例外


例外処理をロジックとして扱う

例えば、「データが存在しないときに例外を投げる、それを一つ上の階層で try catch で取得し、別のデータで補完して、その後の処理を続ける」などが該当する。

例外を投げずにそのまま別のデフォルトデータ(ただし null は除く)を入れて返せばいい。いったいなぜ例外を投げて、使用側という上の階層で処理する必要があるのか?

「本当の例外」ではない、想像できるロジックや機構、エラーにならないものを例外として飛ばしてはいけない。

例外というのはプログラムのメインロジックを中断する事故が発生するようなものに利用してほしい。goto文のように使うのは罪である。


例外の代わりに null を返す

言語によっては「throwしたくないので return null をする」という事が可能だが要検討したほうが良い。いわゆる NullPointerException との恐怖と戦わなければいけないからだ。

nullの話をするならば、PHPやJavaScriptなどの言語では、intを返す前提のメソッドでnullを返したりすると、その後その返り値がintなのかnullなのかをチェックしなければいけなく、実行時エラーの恐怖がやってくる。ゆえにnullは使わなくて良いなら使わないほうがいい。

ScalaやPythonではnullの代わりにNoneを使うべきであるし、Golangではnilの代わりに https://godoc.org/github.com/guregu/null というパッケージを使うとnilを使わずに対策ができる(名前がややこしいが、このパッケージは一般的なnullとは違う)。

Kotlinなどの新しい言語ではnull安全が普及しつつあるため、数年前と比べるとはるかに安全性は高まってはいる。

https://qiita.com/koher/items/e4835bd429b88809ab33

ケースバイケースだが、null安全ではない言語では例外の代わりに return null をするのは避けたほうが賢明である。素直に例外を飛ばしたほうが実行時に動作が安定・安全になるし、予期せぬ障害も減るだろう。


例外を握りつぶす

try{...}catch{} という、catch節で本当になにもせず、ログも出さず、ただ例外を握りつぶすというコードを見る。

errorログに出力せずに例外を握りつぶす事、無かった事にするのはやめよう。バグが起きた時、問題が起きた時、調査のときに非常に困る。本番にエラーログすら出ていない虚無感は計り知れない。数年後に第三者が困ることを想像してほしい。

Golang などでエラーを _ で握りつぶすケースはよく見るが、そこでもしも問題が起きてしまった時、調査を行うときにどうするか?を考えたほうが良い。

例外とはイレギュラーなもので、エラーとはイレギュラーなものが原則である。基本的にログに出力してほしい。無かった事にしないでほしい。

「エラーログが多すぎて大事なエラーが埋もれるのでログを消す」というケースがある。それは本当に例外に出すべきなのか?ロジックではないのか?を再確認したい。本当にエラーが多いならそのエラー対策に務めるべきだし、エラーログを分離するべきだ。例外を握りつぶすべきではない。

もしも無かった事にしていいのであれば、それは例外を使うのがおかしく、エラーにする事がおかしい設計である。例外を握りつぶして無かった事にするのは罪である。


HTML


DBやKVSにhtml自体を文字列として保存する

DOM構造を変更したい時、デザインを変更したい時、CSS(class)を変更したい時などにいったいどうするつもりなのか。テンプレートという便利なものがあるのに、それを使わない理由はなぜなのか。第三者が修正したいときにどうするつもりなのか。今後デザインが変更されない確証がどこにあるのか。

このような愚行を行うと、1つdiv要素を増やすだけで血を流すような面倒な作業が発生する可能性があることを想像してほしい。その後の修正コストが何十倍に膨れ上がるのか見当もつかない、拡張性ゼロの断罪すべき所業である。DBやKVSにHTML構造を保存する行為は罪である。

百歩譲ってキャッシュ戦略だとしても、専用のテーブルやKVSに必要なデータのみを保存すれば良いと思う。そこにHTMLという「デザインを含む概念」を残す必要は無いし、データのレイヤーとデザインのレイヤーは分けるべきです。


(平のJavaScript)ソース内に<div>要素などを書き、HTMLに挿入して組み立てる

※ReactのJSX、Virtual DOM などのレンダリングを使う時は根本的な設計思想が、平のJS等とは全く異なるので除外する。

世の中にはテンプレートエンジン・レンダリングエンジンというものがある。JavaScriptのビジネスロジックコードにHTML構造を残すのは最小限にしたい。

理由は最終的にHTMLで出力されるものが非常に分かりにくくなるからである。

JavaScriptのソース内に直でHTML要素をそのまま書くと、ただの文字列と認識されてしまいエディタやIDEの支援機能も使えなくなるし、JavaScriptとHTMLという異なる概念の切り分けも出来なくなる。

極端な話を言うと、 $('#id').prepend('<div>insert</div>'); のようにdiv要素を埋め込むべきではなく、<div>insert</div> はHTMLやテンプレートコード上に存在した方が良い。JavaScriptのソース上に書かなくても、JavaScript上でDOM要素を取得できれば、望む場所へのDOM要素のコピーや移動は可能である。

とはいえ、一行くらいなら別に良いと思う(コストとのトレードオフも考慮して)。でもそれを大量行数やるのは罪である。

もしもこれをやりたいなら、古いJSをReactに移行してから・・・


APIのレスポンスにHTMLを返す

※node.jsなどで意図的にSerevr Side Renderingを行う設計の時は除外する。

ボタンクリック時にモーダルやダイアログを表示する機構のパターンなどでよく出てくる。

Jsonのレスポンスでデータだけ渡すのではダメなのか?と思う。Jsonで取得し、それをHTML上に挿入すれば解決する。

こちらも、最終的にHTMLで出力されるものが非常に分かりにくくなり、エディタやIDEの支援機能も使えなくなるパターンが非常に多い。

APIでHTMLを返すときに、そのHTMLのベースはバックエンドのソースコード内にString形式でベタ書きされてるパターンがある。いったいテンプレートとはなんなのか?を考えさせられる愚行である。

将来そこに追加改修をする第三者の気持ちを考えてほしい。HTMLがちゃんとHTMLとして壊れていないか?を考慮するために、毎回APIを実行しなければいけなくなり、さらに全てStringで書かれてるためにIDEの支援も受けられなくなってしまい、HTMLのDOM構造が壊れるリスクが高まる。

将来、改修する時の追加改修コストが膨れ上がること、修正する第三者が苦しむ気持ちを考えてほしい。罪である。


DB


外部キー制約を使わない

このくらい良いだろうと思って、やりがちである。

が、考えてほしい。外部キーを使わないということは全てのコードにそのIDが存在する事を確認するためのバリデーションが必要になる。その大変さを本当に理解しているのか?と。

もしもIDが存在しないデータをInsertしてしまうバグを第三者が後の拡張修正で入れた時にどうするつもりなのか?そのテーブルを設計したあなたに問題があるのです。

なお、ユニットテストにどうしても支障が出る場合、SET FOREIGN_KEY_CHECKS = 0 で一時的に外部キーを無効にすることも可能である。

負荷対策のためにDBを分けなければいけなったり、シャーディングが必要になる、マイクロサービスによるデータレイヤの疎結合化など、外部キーを使えないやむを得ないケースは多数存在する。

だが意図せず、サボって外部キーを使わないことは、後にコーディングを大変にする要素と、原因特定が困難なバグを生み出す可能性を増やす罪である。


null default nullのカラムを乱用する

データが存在しない可能性のあるカラムは極力最小限にしたほうが良い。

大多数のnull default nullのカラムを大量に使うテーブルは、(例えば、master table、relation table、transaction table のように)分類し、複数テーブルに分けることが出来るであろう。0にすることは難しいが、他テーブルへ隔離することで最小限にすることは出来る。

データが存在しないnullの可能性があるカラムを常に考慮することは、コーディングする上でも想像以上に大変である。ゆえに極力最小限にすべきである。

データ設計は一度失敗すると後からリカバリすることは非常に難しい。なにも考えず1つのテーブルに全データをぶち込むのは愚行である。


JSON型を乱用する

クエリの更新、取得の難易度が跳ね上がる、カラムの型でデータの規制ができなくなる、indexが効かないなど無数に問題が存在する。

JSON型は最後の手段として使うべきで、99%の問題では使うべきではない。

ただし、情報量不明、型不明などの局所的なケースにおいてはJsonのまま保存すべきケースもあるので、そういうケースでは活用すべきである。(例えば無数の設定値の保存など)

だが、そもそも普通にテーブルとカラムを定義するではダメなのか?本当にJSON型じゃなければいけないのか?代用できる手段はないのか?それを考えてから使ってほしい。安易にJSON型を使うのは罪である。


DAO、ORMを使う場所、SQLの作成場所を考慮しない

シンプルに考えると、MVCならばModel層でDB操作系は行うべきだし、DDDならInfrastructure層でDB操作系は行うべきだと思う。(たまーにシステムによっては例外もあるが省略する)

ViewやControllerにビジネスロジックを書くべきではないし、ましてやDAOやORM、SQLを書く場所にするべきではない。

別にMVCでもMVVMでもDDDでもなんでもいいが、最低限のレイヤーごとの責務を分けてほしいというだけの簡単な話である。

平然とControllerにSQLを生で書く人がいる。ビックリである。罪である。


findのような参照系の名前がついたメソッドの中でデータ更新処理を行う

最終的には検索結果を返してるのでメソッド名自体は嘘ではないが、誤解を招く。

これはメソッド名の名前付けの問題になるが、開発者がfind, selectといったREAD系のメソッド名を見たとき、そのメソッド内の挙動はデータの更新や破壊性がなく、「データに対する更新影響が一切ない」ものと捉えてしまいやすい。

そのため、その中で実は create, update, delete などの処理をしていた場合、開発者が予期せぬデータ更新のトラブル、データ破壊の障害、Writingの高負荷が発生して障害に繋がるケースを多々過去に見た。

ただ可読性として、例えばKVSをキャッシュとして利用する際に、find というメソッド内で「内部でKVSに保存しDBのレスポンスをキャッシュすることを前提とする」事を許容した方が可読性が上がるパターンも多々あるため、最終的にはチーム内のコーディング規約で決めるものになります。


マイクロサービス


ローカルで実行・動作検証できない

マイクロサービス化しすぎたために「dev環境にいちいちアップできないと、実行も確認も出来ない」という開発スタイルが存在する。

非常に辛い。開発がしにくい。システムのスケーリングも重要だが、開発しやすくすることは目的にならないのか?というか、自分達がローカルで実行・動作検証できない事が苦しくないのか?

「障害が起きたときに影響範囲を最小限にする」事や「スケーリング」のみ重視して、開発者本人達がローカルで実行・動作検証できなくする事は愚行である。必ずバグを生み、障害を起こす。開発スピードもかなり落ちる。

dev環境のマイクロサービスをローカルで他のマイクロサービスと連携することができず、且つ ローカル環境にマイクロサービスを立ち上げて開発できない。結果、毎回dev環境にデプロイしないと開発できない なんていうマイクロサービスなどやめたほうが良い。

ローカルで実行・動作検証できないシステムを生み出す行為は、かなり保守が困難になる。大罪である。

ただ、アプリケーションによっては複雑過ぎてDockerを駆使してもローカルで構築しきれなかったり、ロードバランサー関連の検証、複数台のSlaveの検証、サーバーのスケールアウトの検証、ブラックボックスされた古のどうやって動いてるのかも分からないXXXを使わなければいけないなど、ローカルでは動作検証が不可能・コスパが悪すぎることも多々存在するし、こういうケースはやむを得ないと思う。でも出来る限りシンプルな実行はローカルで行えるようにしたい。


マイクロサービス間で、同じDB・KVSのデータを扱う

「マイクロサービスAでinsertしたデータを、マイクロサービスBでupdateし、マイクロサービスCでselectする」などが該当する。

マイクロサービスごとに、使用するDB・KVSのテーブルデータを分けないと、互いにマイクロサービス間で影響しあい依存度が密になる。各マイクロサービスが独立しなくなる。できるだけマイクロサービス間は疎結合にするべきだろう。

サービス間で密に依存しあい、互いに干渉しあうそのサービスはマイクロサービスといえるのだろうか?ただコードと実行システムを分けただけのものをマイクロサービスとは個人的には呼びたくない。

データレイヤ、インフラストラクチャレイヤのレベルで完全に分離し、データの流れはAPI、gRPC、pubsubなどのリクエストとレスポンスのみでやり取りするというのが基本であってほしい。

望むならばマイクロサービスは出来る限り疎結合であってほしい。

複数のマイクロサービスで、同じDB・KVSのデータを更新・参照し、その状態変化で互いに密に影響しあうのは罪である。


リリース


本番のデータを扱わないと動作確認や検証ができない

リリースするまでちゃんと動くことを神に祈ればいいのか?その状況をおかしいと思わないのか?そういう設計にする事を極力やめるように努力するのは基本ではないのかと思う。

自分以外の第三者が開発するときに困るという事を考えてほしい。その機能の保守を第三者が行うときに苦しむ事を想像してほしい。

そもそもdev環境ですらまともに確認ができない機構を、本番にいきなりデプロイ・リリースしてバグもなくシステムが安全に動くわけがない。

やむをえないケースは存在する。だが、殆どのケースでは本番のデータを使わないと動作確認できないということは理想的にはありえない。色々なケースが歪んでその状況は発生してしまっているのだ。

代わりにmockサーバーを使えばいいし、テストなどでも保証はできる。せめてdev環境・staging環境で検証できるようにすべきである。本番(production環境)にリリースするまで全く動作検証できない状況を作り出すのは大罪である。


リリース時期を言い訳にして、問題のあるコード・設計力の低いコードをマージしようとする

問題のある設計・コードのまま、リリースするとその後に工数が数倍に膨れ上がることを想像してほしい。

良い設計でも時が経つとコードは腐りやすいし、完璧な設計などなかなか出来ない。これは万人が悩み続ける問題である。しかし、明らかに有害な設計・コードは、その腐る時間が数倍短くなる。システムに毒を埋め込んでいるのだ。時限爆弾を埋め込んでいるのだということを自覚してほしい。

リリース時期・納期が決まっていても、良い設計は出来る。リリース時期・納期・締切のせいではない。設計力とスケージューリング能力に問題があるのです。そして出来ればレビューの前に、ソースコード開発時にその問題は解決してほしいし、レビューの前に相談してほしい。リリース直前に議論することではない。

そのままリリースするとその罪な設計・コードのせいで、将来第三者が苦しむことを想像して下さい。その問題を第三者に押し付けないで下さい。

それをわかった上で、その罪・毒・時限爆弾を埋め込みつつリリースするかどうかを、上長や担当者と話し合い決めるべきです。いったんリリースしてからリファクタするという選択肢は可能ではあるが、現実として経営判断的に後回しになることが多いです。さらにリファクタを今すれば1日で終わるものでも1年後に修正すると1ヶ月かかるかもしれません。

機能追加もそうです。ほぼ全てのシステムは成長します。Xヶ月後の機能追加案件が今修正すれば1日の工数で終わるものでも、罪が仕込まれたコードでは1ヶ月の工数がかかるかもしれません。

それを想像して下さい。大罪です。

まだまだあると思うが、一旦このへんで。(思い出したら追記していく