こんなソースコードは嫌だ
作成日時:2023-01-17
更新日時:2023-01-17
いま私が出向しているプロジェクトのソース(運用含め)が余りにも酷いので反面教師として内容を記載する。
と思っていたが書いていくうち、ほぼ愚痴みたいになってしまった。
基本Javaベース。Webアプリ。
1.クソコード集
1クラス10,000行over
ふざけとるんか。
何かしらの理由があるならギリギリ許せるがこのプロジェクトにそんなものはない。
どうすべきか:
- クラス分割する。
- SOLID原則のS単一責任の原則。
ネスト、ネスト、ネスト
if (...) {
if (...) {
if (...) {
if (...) {
if (...) {
// まだまだネストするよ!
}
}
}
}
}
これに加えてインデントが揃っていないという地獄。
訳アリでフォーマッタも使えない状況。
どうすべきか:
- if減らす。深くネストするな。
- 早期returnする。
- フォーマッタ掛ける。
- メソッド分割する。
- これを読む。
分岐アンチパターン - Qiita - 個人的に極論を言えばif文使うな。列挙型や多態性とか駆使して消す。
マジックナンバー
if (kbn == 1) {}
区分「1」って何ですか。区分「1」って何の区分なんですか。ねえ。
このプロジェクトは設計書が無いんすよ。
どうすべきか:
- マジックナンバーはやめろ。
- 列挙型とか定数で定義して。数字に意味を持たせて。
- できないならせめてコメント書いて。
でもコメントだけだと区分の意味が変更されたら嘘のコメントになるので、やっぱり列挙型とか定数とか使ってほしい。 - コード区分とかのデータの集合は列挙型。グループ的な物ではない奴→定数という感じか。
- エラーメッセージ系の文言は外部ファイルやDBとかでまとめて定義してそこから取得して。
メソッド定義
public void generatePin(){}
public void generatePin2(){}
generatePin2とは?2とは何ぞや。思いつかないからって適当に数字つけないで。
無印との違いはなんなのさ?
いちいち定義を見に行かねばならん。
どうすべきか:
- 一目でわかるメソッド名にする。
- JavaDoc書け。普通のIDEならメソッドにマウスオーバーすれば、JavaDocの内容が表示されるから。
いちいち定義を見に行かなくても処理の概要が分かる。 - 命名が苦手ならcodic(https://codic.jp/)とか使って。
命名規則の記法
int a_num = 1;
int bNum = 2;
method_snake();
methodCamel();
記法統一して。camelなのかsnakeなのか。
色々なところから呼ばれる内部クラス
class OuterClass {
public static class InnerClass {
public static void method(){};
}
}
// call
OuterClass.InnerClass.method();
InnerClassがprivateでOuterClassからのみ使われるなら分かる。
でも外部から直で呼び出されている。
何のために内部へ定義した?外に出して。
OuterClass.InnerClassといちいち書くのが面倒。(書かずに済む方法があるが、だからと言ってそれを使うな。)
どうすべきか:
- 色々なところから呼ばれるクラスなら内部クラスにするな。(理由があるなら別にいい)
操作可能にならないプルダウン
画面が表示されてからAjaxでプルダウンの中身を取得しているが、それに2分くらいかかる。
そのため2分間、プルダウンは操作不可能(何も出てこない)
原因:
データ取得のSQLが遅すぎる。
Q. 何故遅いのですか?
A. 全テーブルにインデックスが存在しない。フルスキャン+結合処理で尋常じゃないコストが発生。
どうすべきか:
- インデックスを貼れよ。DB設計時に何も思わなかったんだろうか。
- SQLを書いたらアクセスプランを採れ。
フルスキャンしてたりインデックスを使っていないならクエリ見直せ。 - インデックスが存在しない事に誰も何の疑問を持たないのは何なの。
- スロークエリログとか出しといて。
インデックスを貼らない理由があるならば貼らなくても良い。
- インデックス使った検索よりフルスキャンの方が速い。(行数が少ないからとか場合によってはそうなる)
- インデックス作ると容量を食うから。(なんか資源が限られている場合ならまあ分かる)
- 登録/更新に時間がかかるから(インデックスの再構成らへん)。とか
N + 1問題
var list = 対象のデータ一覧をDBから取得する処理();
for (listの件数文ループ) {
listのデータの詳細データをDBから取得する処理();
}
何回もDBへアクセスしに行くな。
どうすべきか:
- join使うなりして1回で全部持ってこい。何回もDBへアクセスしに行くな。
- 但し、テーブル設計/インデックス等の問題で一括取得SQLが結合処理などにより遅い場合はN+1のアクセスもありだと思う。
一括取得より速い場合もありうる。
さっき取得したでしょそれ
var idList = [1, 5, 3, 1];
for (var id of idList) {
DBからidに紐づくデータを取得(id);
}
ID:1のデータ取得処理2回呼んでる。
どうすべきか:
- 何回もDB取得処理呼ぶな。
- 重複省くなりしろ。
- キャッシュするなりMapに突っ込むなり保持して取得済みならスキップしろ。
タイムアウト
DB.connect();
for(連携されたファイル数分ループ){
時間かかるファイル処理();
}
DB.close();
このコード書いた人、ファイル1件とかでしかテストしなかったんだろうか。
STG環境で複数ファイル流したらタイムアウトが発生してDBコネクションがクローズされない→プール枯渇→死で顧客から連絡来たよ。
どうすべきか:
- 時間内に処理を終わらせられるか考慮
- 仮にタイムアウトした場合、問題が発生しないか。安全に終了されるかを考慮
- 並列化して高速化する。ただし安易にそうするな。並列化は色々面倒だから。
- テストちゃんとやれ。最大件数とか負荷試験/異状系とか。
変数の命名とスコープ
var a = xxx; // 使用する直前に初期化しても問題ないのに。
.
.
数百行のロジック
.
.
a.method(); // 初めてaを参照。
勘弁してください。
どうすべきか:
- 使う直前に変数定義して。
- 変数名aって何?スコープが小さいならいいが大きいなら意味を持たせて。
- スコープ小さくして。
共通化してくれ
app1
└─Util.java
app2
└─Util.java
app3
└─Util.java
app4
└─Util.java
上記の4アプリ(パッケージ)は大体似たような内容で、その中のUtil.javaも同じ内容だが、ライブラリとして抽出していない。
なのでどれか1つを直したら別の3つにも適用しなければならない。
(もう最近はそれすら放棄していて名称やパッと見の振る舞いは同じなのにappごとに方言のような若干の差異がある。地獄。)
どうすべきか:
- 共通ライブラリ作って。
- 複数のモジュールにある似たような処理はメソッド化して共通クラスとかどっかに置いておいて。
- かと言って、何でもかんでも共通化するのはまずい。
他にも問題点が大量にあるけど、もはや記事にするレベルじゃない内容ばかりなので割愛。
(XSS実行可能とか、スレッドセーフじゃない処理とか、参照型の比較に等価演算子使ってるとか、フレームワークも設計書もコードレビューも存在しないとか。)
2.こうならないためにはどうすべきなのか
正直、ここまでの酷さは普通にやってればならないと思う。
とにかく個人/会社ともにコードの品質を高めるようにしていくべき。
個人:リーダブルコード読むなりしてコードの品質を高める。
会社:コードの品質を高める仕組み/枠組みを作る。
自分がコーディングする際に気にすること
基本的に下記を守るようにコーディングしている。出来ているかはともかく。
(出向先にちゃんとコーディング規約があるならそっちを遵守)
(例外もある/やりすぎもNGなのでケースバイケースで)
DRY原則
- 情報を重複させない。共通クラスとかに関数を作ってまとめるとかする。
- 仮に何度も同じ記述をして、その箇所に改修があったら全部直す+検証しなければならないよね。
KISS原則
- Keep it simple, stupid.(シンプルにしておけ!この間抜け)
- 多少パフォーマンスが下がるとしても私は可読性を重視している。
- 無駄なネスト/何百行もあるメソッドとか無くす。
- 拡張性/再利用性/テストの組み立てやすさとかいいことあるよね
- 個人的にはネストは0-3個。メソッドは60-100行越さないのを目標に組む。
(ビジネスロジックが複雑なら達成できないこともあるけど)
YAGNI原則
- 余計な機能は作らない。
SOLID原則
- 正直全部理解していない。
- SとOは守るようにしている。
コメントやJavaDocをちゃんと書く
- ソースを見てパッと処理内容が理解できない箇所にコメントを書く。
- 分かり切った処理にはコメントを書かない。邪魔だから。
- 自分や他人のために書け。
命名規則をしっかりと
- 正直センスが無いので苦手。
- だがちゃんとやれば開発楽になるよね。それを見るだけで内容が分かるようになるから。
デザインパターン
- 勉強中(2個しか知らん)。でもなかなかデザパタを適用できそうな場面に会えない。
私は特に1256を厳守。
大雑把なまとめ:
「同じ処理書かずにシンプルで余計な機能はつけず、コメントと命名規則ちゃんとやって何かいい感じにする。」
リーダブルコード
読め。
コードの品質を高める仕組み
私がSESで出向した会社の内、品質が高かった会社はコードの品質を高める仕組み/枠組みがきちんと確立していた。
下記がちゃんとある。
- コーディング規約
- フォーマッタ定義
- チェックリスト(ベストプラクティスとアンチパターンのまとめ)
- コードレビュー
- テスト自動化
- etc…
逆にこれらが全て存在しない今の私の出向先の品質は最悪なものになっている。
良かった出向先のフロー例
- コーディング規約に則り、コーディングする。
- コードレビュー前にチェックリストを確認する。
アンチパターンな記述があれば修正する。 - フォーマッタをかける。
- コードレビューを受ける。
- 指摘があり、かつそれが頻出する指摘事項であればチェックリストに追加(ナレッジ蓄積)
1-5を繰り返す。
チェックリストは他のプロジェクトでも使いまわす。どんどん熟成していく。
これらをやっていた会社で発生した不具合は、仕様認識相違が起因のものぐらいでコーディングミス等の不具合はほぼ無かった。
コーディング規約/フォーマッタ
- 言わずもがな。
- 汚い/見づらい/統一性の無いソースは色々あとで困る事になる。
チェックリスト
- その企業で熟成されたコーディングのベストプラクティスとアンチパターン集(≒コーディング規約)。
- 実装時/実装後はこれを確認させることでアンチパターンを作らせないようにする。
- コードレビューにおいては頻出アンチパターンの指摘に時間を取られたくない。
- そっちに時間取られるより、機能/非機能要件や詳細設計どおりになってるかとかをレビュー時に考えていたい。
- なのでチェックリストを作成し確認させることで、頻出アンチパターンはレビュー前に潰す。
テスト自動化(xUnitとか)
- 作っておけば改修時のデグレを早期検知できるね。
テスト自動化の損益分岐点
- テスト自動化のコスト < テスト自動化の恩恵(リリース頻度と回数による) になるよう。
- 頻繁にリリースするシステムなら恩恵が大きいのでテスト自動化しておくべき。
- そんなにリリース回数が多くないなら自動化の恩恵は少ない。無駄にコストがかかる。
- 損益分岐点を考える。