メソッド分割の基準
メソッドの分割基準について
今回は、とある public メソッドを完成させようとした場合にどのような基準でロジックを private メソッドに切り分けていくか、という話です。
メソッドを分ける目的について
前の記事にて「クラス分けなんて整理術なんです」と書きましたが、メソッドを分けるのも同様に単なる整理術でしかありません。
全体で50行くらいの小さなプログラムであれば1メソッドに全て書いてしまって問題ありません。
ではプログラムが大きくなった場合、何を目的としてメソッドを分けるのでしょうか。
私は以下の目的でメソッドを分割します。
- 処理の共通化
- 短期記憶容量の節約 (によるバグ混入率の軽減)
- 処理の抽象化による整理
- 処理の抽象化による変更への備え
この内の一つだけを理由にメソッドを分割することもあれば、複数を理由にメソッドを分割することもあります。
処理の共通化
同じメソッド内で複数回実行したい複数のメソッドで同じ処理が実行される場合、その処理を1つのメソッドとすることで保守性を上げます。
これをやらない場合、その処理に何か変更が入った場合に複数個所を修正する必要があり、修正漏れが発生することで修正工数が上がったりバグが混入する確率が高くなったりします。
ちなみに処理を共通化する場合は複数の個所に散在するロジックが、本質的に同じものを意味しているかには注意する必要があります。
例えば
BigDecimal get医療費計算(BigDecimal source) { BigDecimal 消費税率 = new BigDecimal("0.1"); BigDecimal 75歳以上医療負担率 = new BigDecimal("0.1"); (なんやかんや計算する処理) }
という処理があった場合に、new BigDecimal("0.1") が同一であることに着目して
BigDecimal 医療費計算(BigDecimal source) { final BigDecimal 消費税率 = get10percent(); final BigDecimal 75歳以上医療負担率 = get10percent(); (なんやかんや計算する処理) } private BigDecimal get10percent() { return new BigDecimal("0.1"); }
と分割してしまった場合、消費税率が15%になったり75歳以上医療負担率が5%になったりした場合に修正が面倒くさくなります。
(もし get10percent() の中身を書き換えると、変更されていない方に影響が出てしまう)
そのため、処理を共通化する場合はそれらが本質的に同じものであるかに気を付ける必要があります。
短期記憶容量の節約 (によるバグ混入率の軽減)
私は学生時代に心理学専攻であり、短期記憶を研究しているゼミに所属していました。
そのため普通の人より短期記憶については詳しいのですが、実は人間の短期記憶容量というのは案外小さいです。
普通の人は一度に4つのことしか覚えていられません。
(一昔前に「マジックナンバー7±2」という触れ込みで短期記憶容量が 5-9 という言説が流行しましたが、アレはデマです。とある学者が「そのくらいじゃないの」とテキトーに言った数字が独り歩きしてしまっただけです)
しかも覚えている量が大きければ大きいほど、それ以外の処理能力が落ちてミスをしやすくなります。
つまり、何か1つのことを覚えながらプログラミングする場合と、何か4つのことを覚えながらプログラミングする場合とでは、後者の方が処理能力が落ちます。
結果としてミスをしやすくなり、バグの混入確率が上がります。
プログラミングをする際には一時的に記憶しておくものは少ない方がいいのです。
ではプログラミング中に一時的に記憶しておくものとは何でしょうか?
それはメソッド内で宣言されるローカル変数です。
メソッド内のローカル変数が少なければ少ないほど、そのメソッド内でのミスが減ります。
これに基づいて考えると、
double calc() { final double a = getA(); (中略。a を利用した処理) final double b =getB(); (中略。b を利用した処理。a は登場しない) }
という構造になっている場合は
double calc() { return calcA() + calcB(); } private double calcA(){ final Integer a = getA(); (中略。a を利用した処理) } private double calcB(){ final double b =getB(); (中略。b を利用した処理。a は登場しない) }
といった感じでメソッドを分割するとよいです。
こうすることで各メソッドを読む際の短期記憶容量が節約され、ミスをしづらい & 読みやすいコードになります。
処理の抽象化による整理
これについてうまく説明できる自信がなく、逃げるようでアレなのですが、処理の抽象化についてはダイクストラの「構造化プログラミング」に関連する各種記事を読むとよいです。
抽象化の力を借りて「より良い」プログラミングをしようとしてきた歴史を学ぶことができます。
ここでは wikipedia 先生の力を借りることにしましょう。
ダイクストラはプログラムの正しさに対して証明を与える従来の研究を分析して、証明の手続きを考えずに書かれたプログラムは証明に必要な労力がプログラムのサイズに対して爆発するとし、「与えられたプログラムに対してどうやって証明をするか」ではなく「証明がしやすいプログラムの構造とは何か」についてフォーカスするとした。 (中略) その上で、この例のように詳細なプログラムを抽象化(abstraction)していくのではなく、逆に抽象的なプログラムから始めて詳細化(refinement)していくというやり方を示している。 詳細化の際には共同詳細化(joint-refinement)という考え方が示されている。これは抽象データ構造の詳細化と共にそれを扱う抽象ステートメントを同時に詳細化し、それを1つのプログラムテキストのユニットに分離するというものである。このユニットをダイクストラは真珠(pearl)と呼んだ。また、抽象的な真珠が1段階具体的な真珠に依存し、その真珠がさらに具体的な真珠に依存していったものをネックレスに例えた。そしてネックレスの上部は下部に関わらず正しさを証明することができ、また下部を取り替えることでプログラムのバリエーションを労力をかけずに作れるとした。
なんのこっちゃという感じなので例を出します。
例えばカレーの作り方をプログラミングする場合、
void makeCurry() { 野菜や肉を切る(); 野菜を炒める(); 肉を投入し、肉にも火を通す(); 鍋に水を入れる(); カレー粉を入れる(); 混ぜながら煮込む(); }
となるとします。
そうした場合、仮にこれが
void makeCurry() { カレー粉を入れる(); 肉を投入し、肉にも火を通す(); 野菜を炒める(); 混ぜながら煮込む(); 野菜や肉を切る(); 鍋に水を入れる(); }
というめちゃくちゃな順序になっていると、一目でプログラムが間違っていることに気づくことができます。
実際には各メソッドの中身は詳細な手順が書かれています (例えば野菜や肉を切る() はどの野菜をどのくらいの大きさに切るかなどが記載されている)。
しかし抽象化されたメソッド名を読むだけで、詳細部分を読むことなくプログラムの全体感の妥当性を確認できます。
(ちなみにこのとき、「抽象化」された各メソッドはそれぞれ正しく動くことを前提として読みます。仮に「混ぜながら煮込む()」メソッドで混ぜてない (メソッド名が嘘をついている) 場合は前提が破綻します。プログラマが名前付けにこだわるのはこれが理由です)
もちろんこれだけではプログラムの全てが正しいことを確認することはできませんので、その後に各メソッドの詳細を確認する必要があります。
しかしそれぞれのメソッドを読む際の読み方は全体感を見る場合と同じ (処理の抽象度が違うだけ) であり、個々個別の分かりやすさは同様です。
処理を抽象化し、整理することで全体を把握しやすくなり、より分かりやすいプログラムを書くことが可能になるのです。
処理の抽象化による変更への備え
将来、明らかに変更されるだろうと予測される場合は予めメソッド化しておくこともあります。
BigDecimal get医療費計算(BigDecimal source) { BigDecimal 消費税率 = get消費税率(); (なんやかんや計算する処理) } private BigDecimal get消費税率() { return new BigDecimal("0.1"); }
上の例では、消費税が変わる場合に備えて消費税率を取得するメソッドを作成して、そこから消費税を取得するようにしています。
これにより、消費税が変更された場合に、プログラムも簡単にその変更に追随することが可能になります。
人によっては YAGNI (You Ain't Gonna Need It) の原則に反すると言うかもしれませんが、明らかに予測されることであれば予め備えるのは不自然なことではないと思います。
メソッドを分けるデメリット
1メソッドは1-3行以内にすべき、という極端なことを言う人もいますが、私はそうは思いません。
メソッドを分けること自体にもデメリットは存在します。
- 1段階スコープが上がる
- 処理が細切れにされ、どこで何をしているのか分からなくなる
先ほどメソッドを分割する目的を挙げましたが、それらの恩恵を受けられないメソッド分割の仕方ならばデメリットを考慮して分割しないという選択もできるようにしておきましょう。
1段階スコープが上がる
これを意識している人は少ないのですが、メソッド内に処理をベタ書きしている個所を private メソッドに切り出した場合、そのクラス内からその処理を呼び出せるようになります。
つまり処理のスコープが上がります。
その処理はクラス内のどこから呼ばれても 正しく 動かなくてはなりません。
どこから呼ばれても正しく動くようにするために考えるコストが上がります。
例えば元のメソッドではそのメソッド内の前提のみを考慮して計算すればよかったところ、どのような前提でも動くようにしなければなりません。
そのために処理を抽象化する必要がありますが、その抽象化に失敗する可能性があります。(人間なので一定確率でミスをするのは仕方ないことです)
私は今までに何度も、メソッド分割前の前提を捨てきれていない private メソッド、つまり他の個所から呼びだすとバグを誘発してしまうメソッドを見たことがあります。
処理が細切れにされ、どこで何をしているのか分からなくなる
処理が細切れにされるのはメリットでもありますがデメリットでもあります。
本質的に密結合なものを細切れにすることで、むしろメソッド分割前よりも読みづらくなっているコードを見ることがあります。
例えば
BigDecimal calc() { BigDecimal ret; final BigDecimal a = getA(); (中略。a を利用して ret の操作する処理) final BigDecimal b =getB(); (中略。b を利用して ret の操作する処理。a は登場しない) return ret; }
というメソッドがあるとしましょう。
短期記憶容量の節約の章で似たような例を出しましたが、ここで違うのは ret がメソッド全体で操作されているということです。
この場合、メソッドは分けない方が分かりやすいです。
a を利用する個所と b を利用する個所でメソッドを分けると、ret がどのように書き換えられているのか分かりづらくなります。
例えば a を利用する個所を書き換えた場合に b を利用する個所も書き換える必要が生じるかもしれないのですが、メソッドが細切れにされているとその必要性に気づきづらくなります。
このような場合は多少長くなっても、短期記憶容量が圧迫されるとしても、全体の処理を1メソッドとして書く方がバグが混入しづらくなります。
最後に
クラス分割にしてもメソッド分割にしても、ちゃんと自分なりの合理的な理由を考えて行うようにすべきです。
巷には分かりやすく「クラス (メソッド) は何行以内」みたいなことを言う人がいますが、そういうものを真に受けてはいけません。
そういう言説 (や社内ルール) は、何も言わないと分割せずにだらだらと1000行でも10000行でも書き続ける人に向けられたものです。
まともなプログラマを目指すのであればそのような言説に惑わされず、どのようにすれば目の前のコードが分かりやすく and/or 読みやすくなるのかを考え続けるようにしましょう。
クラス分割・メソッド分割の基準
本日はクラス分割・メソッド分割の基準について書こうと思います。
昨日の続きとして「潔癖症プログラマは面倒くさい (メソッド行数編)」みたいなタイトルにしようかと思ったのですが
私の分割基準を書く方が有意義だと思ったのでタイトルを「クラス分割・メソッド分割の基準」としました。
ちなみに私は仕事で主に Java を利用していますので、以下は Java での話だとご理解ください。
想定読者はプログラミング歴1~2年程度でしょうか。
クラスの分割基準について
1クラス50行にすべき?
いいえ、そんなことはありません。
1クラス50行というのは全くもってアホな基準です。
たぶん世に出ている基準として一番厳しい基準がこの「1クラス50行」だと思うのですが、一昔前に『オブジェクト指向なんとかエクササイズ』みたいな名前の本の中で書かれてて、ちょっと話題になったんですよね。
で、当時の若い人がそれを信じて実践しちゃったりしていました。かわいそうに。
気になる人は「1クラス50行」でググると結構出てくるので読んでみるといいかもしれません。
ただ行数でクラスを分割するのは全くもって本質的な分割基準ではないので騙されないように。
クラスの形態
一口にクラスといっても使われ方は様々です。
私は大きく以下の 2+1 のタイプに分類して考えています。
- データ (抽象データ型) を表現する箱
- 処理 (メソッド) を書く箱
- 定数を置く箱 (これは無い場合もある)
データ (抽象データ型) を表現する箱
すごく乱暴に言えば、どんなプログラムも何らかの値 (データ) に対し、何らかの処理をするだけです。
つまりプログラムはデータと処理の2要素でできています。
この内、データについて、抽象データ型として表現する機能がクラスにはあります。
抽象データ型とは
抽象データ型というのは、抽象化されてデータ型のことです。そのままですね。
例えば String について考えてみます。
おそらく教科書などでは String = 文字列型 と書いてあると思うのですが、文字列型とは何でしょうか?
そもそもコンピュータは0か1しか扱えません。
それを 8bit ごとに区分けて 1byte とし、
1byte の数列を byte型配列とし、
byte 型配列を一定の基準によって char 型配列に変換し、
char 型配列を文字列型として扱いやすくしたのが String というクラスです。
元のデータは0と1でしかないのに、なぜ我々は String クラスを文字列型として操作できるのでしょうか?
それは String クラスを作った人が、データの処理の仕方をクラス内に隠蔽しつつ、文字列型として必要なメソッドのみを公開しているからです。
つまり「文字列型としてこちらでよろしく処理しておくから、あなたは何をしたいかだけを教えてください」という形式にしているからです。
これにより、我々は String を文字列型として扱えます。これが抽象データ型の考え方です。
そしてこの抽象データ型は、我々プログラマ自身が定義することも可能です。
簡単な例で言うと以下の Point は自分で定義した抽象データ型です。
class Point2D { private final double x; private final double y; Point2D(double x, double y) { this.x = x; this.y = y; } double getX() { return x; } double getY() { return y; } }
この例においては、Point2D は中身自体は double の x と y しか持っていません。
しかしその2変数は private 変数として隠蔽され、Point2D は外から見れば2次元座標データとしての役割を果たすことができます。
これだけだと「x と y が getter で外に露出してるから隠蔽されてないじゃん」と言われそうなので、変化形も出しておきます。
class Point2D { private final double length; private final double radian; Point2D(double length, double radian) { this.length = length; this.radian = radian; } double getX() { return Math.cos(radian); } double getY() { return Math.sin(radian); } }
変化させた上の例では原点からの距離と角度で Point2D を初期化していますが、getX() と getY() は独自に計算しています。
メソッドと変数は必ずしも対応している必要はなく、「そのデータは何であるか」「どのような操作ができるか」を定義しているのが抽象データ型です。
ここまで読んで「このクラスは物足りない。初期化して値を取り出しているだけではないか」と思った人もいるかもしれません。
そうですね、メソッドを増やしてみましょう。
class Point2D { private final double x; private final double y; Point2D(double x, double y) { this.x = x; this.y = y; } double getX() { return x; } double getY() { return y; } Point2D add(Point2D other) { return new Point2D(this.x + other.x, this.y + other.y); } Point2D minus(Point2D other) { return new Point2D(this.x - other.x, this.y - other.y); } }
加算の add() と 減算の minus() を足してみました。
しかし要件によってはこれでも足りないかもしれません。
2点間の距離を求めたり、中間点を求めるメソッドが要求されるかもしれません。
場合によっては Point2D をベクトルとみなした上で、内積を求めたり外積を求めるメソッドを追加することも考えられます。
抽象データ型のクラス設計
前述したように、抽象データ型についてどのような操作を行えるべきかによって実装するメソッドが変わってきます。
通常は、必要と思われるメソッドを (無駄にならない範囲で) すべて実装することになります。
必要なメソッドが少なければ50行以内に収まることもありますし、必要なメソッドが多ければ軽く数百行に達することもあります。
このように抽象データ型のクラスは概念ありきでクラスを定義しますので、行数が増えたからクラスを分割するという発想はありえません。
処理 (メソッド) を書く箱
データを定義したら、残りは処理です。
処理のクラス分けは、簡単に言えば整理術です。
例えば20行程度の極小さいプログラムであれば1クラス1メソッドで収まりますが、これを分類して分かりやすくする技術です。
これについては説明が少し難しいのですが、私の場合はおおよそ以下の方針でなんとなく分けています。
- 依存関係が高い処理は同じクラスに、依存関係が低い処理は別クラスに
- 似ている性質を持つ処理は同じクラスに、性質が似ていない処理は別クラスに
依存関係が高い処理は同じクラスに、依存関係が低い処理は別クラスに
2つの例を考えてみましょう。
(a). すべての処理が1つのクラスに詰め込まれて記述されている状態。 (b). 1クラス1メソッドとして、すべての処理がばらばらに記述されている状態。 どちらも望ましいと言えないとすぐに分かると思います。
(a) については、1クラスが肥大化して、それぞれの処理がどのような関係になっているのか分からないです。
それに加えて、例えばメソッドがメソッドA~メソッドZまで26個あったとして、
- メソッドCはメソッドX を呼び出している。
- メソッドXはメソッドG を呼び出している。
- メソッドGはメソッドC を呼び出している。
といったような無限ループが起こり得る状態になっていても気づきづらくなっています。
(b) は私に言わせれば (a) よりもタチが悪いです。
それぞれ別クラスに書かれているために処理を追うのが面倒ですし、書かれている場所が違うだけで本質的に何も整理されていません。
1 の無限ループ問題が1と同じくらい発生しやすい状態です。
そこで依存によってクラスを分ける考え方が生まれます。
例えば
- メソッドA はメソッドCからのみ呼び出される
- メソッドB はメソッドCと何ら関係がない。 という場合は
- メソッドAはメソッドCと同じクラスの private メソッドとする。
- メソッドBはメソッドCと別クラスに分ける。
ということをします。
これにより、メソッドAの影響範囲はそのクラス内に矮小化されます。
この整理を進めることで、各メソッドの関係性が分かりやすくなり、プログラム全体としての見通しが立ちやすくなります。
似ている性質を持つ処理は同じクラスに、性質が似ていない処理は別クラスに
これは例えば
- データを外部から入力するクラス
- 入力したデータに不備がないかチェックするクラス
- 入力したデータを処理するクラス
- データを外部へ出力するクラス
- 上に挙げた各クラスを適切な順序で呼び出す司令塔クラス
と言ったようなイメージです。
先ほど依存関係での整理について述べましたが、このように性質によってクラスを分けた場合、たいてい依存関係もきれいになります。
(依存関係による整理を先に書いたのは、こちらを先に書くと依存関係による整理を書きづらくなるから。基本的には性質によって分けるとよいです)
現場で培った経験やその現場の文化 (クラス粒度) もありますので、新人プログラマの人は困ったら先輩に相談すればいいと思います。
定数を置く箱 (これは無い場合もある)
定数クラスと呼ばれるものです。
定数は必要なクラスに private static final で宣言されるのが普通ですが、あえて同じ性質の定数を1か所にまとめて置くことで検索性を高めることが可能になります。
面白くないので割愛。
(まとまってないけど) まとめ
所詮クラス分けなんて整理術なんです。
小さなプログラムなら1クラスに全部書いてしまっても問題ありません。
しかしある程度以上大きなプログラムになると、1クラスに書いてられないので分類しているだけなのです。
ですのでクラス分けのこと考える際は、どのように分けるのが理解しやすい分類なのかを考えるのが一番有用だと思います。
行数なんて参考にするかどうかも怪しいような基準ですので、「1クラス50行」みたいなアホな話を信じないように。
長くなってしまったので、メソッド分割については次回書きます。
潔癖症プログラマは面倒くさい (コメント編)
私もアラサーなので、今まで mixi なりブログなりに挑戦してきた経歴があります。
いずれも結果は3日坊主で終わりました。
何故かと言うと論理的な文章を構成するのがすごく面倒くさいからなんですよね。
なので今回のブログは、あまり推敲はせず、多少穴があってもいいので思うことをつらつら書いていく形式にしようと思います。
ここで言う潔癖症プログラマと言うのは、何か信仰 and/or 原理主義にハマっちゃった人たちをイメージしています。
などなど。
別に本人たちが勝手に自分の制約として使う分には構わないのですが、周りに押し付けてくるのがウザったいですよね。
私自身は、現実で潔癖症プログラマに出会った場合は喧嘩するのも面倒なのでテキトーに受け流します。
しかしそういう声が大きい人たちの声に流される若者がいると悲劇が連鎖しますので、おっさんの責務として反対意見を書き残そうと思います。
「コメント絶対書かない」主義について
「ソースを読んだときに、それが何をしているのか分かるように書くべきだ。そうしたらコメントなんて要らなくなるだろ。コメントを書くのは可読性が低いコードしか書けない人間の甘えだよ。さらに言えばコメントがメンテされなかった場合、そのコメントは嘘として負債になるだろ」
みたいなやつです。
実際にここまで強い言葉を使う人はほとんどいないと思いますが、まあ潔癖症の人たちはこう思ってますよね。
思ってないとしてもそう考えていると仮定します。
「ソースを読んだときに、それが何をしているのか分かるように書くべきだ」
これについては諸手を上げて賛成します。
ただこれって別に下記みたいな書き方でも達成できるわけで。
void hoge() { // DBからデータを持ってくる (数行の処理) // データをhogehogeする (数行の処理) // hogehogeしたデータをDBに入れる (数行の処理) }
他の人のブログを見ると、このように処理内容を説明している系コメントはダメコメント扱いされているのですが、個人的にはそれが不思議です。
上の例の「数行の処理」に名前を付けて別メソッドにすれば確かに自己説明的なコードになり、コメントをなくすことができます。
しかし別メソッドに分けるかどうかは、また別の基準があるべきです。
その別基準に従ってメソッドを分けた結果、そのコメントが不要になるなら消してもいいです。
しかし別メソッドに分ける基準を満たさずに1メソッドが20行超になってしまう場合は、上のようなコメントあった方が読みやすいですよ。
(私のメソッドを分ける基準は (1). 抽象化する意味があるか、(2). 複数個所で利用されるか、(3). ローカル変数の生存期間、(4). 見通しのよさ、などです。1メソッド数行信仰みたいな盲目的な判断はしていません。これについては別の機会に書きます)
「(可読性高く書けば) コメントなんて要らなくなるだろ。」
これを聞くたびに思うのが、「この人は一人でしか開発したことないのかな?」「ソースコードの書き方がたった一つしかないと思っているのかな?」です。
コメントというのは、後任者に向けた申し送り事項でもあります。
主に、なぜそのように書いたのか、意図や言い訳を伝える目的で書きます。
(a). 明確な意図を伝えるコメント
// ここで HogeParser を利用すると \ (バックスラッシュ) が読み込まれないので PiyoParser を利用する。
(b). 言い訳を伝えるコメント
// ごめん、時間がなくてハードコーディングしてしまった。
(a) も (b) も、とても有用なコメントです。
「自分が何かのコードを引き継いだときには前任者は退職していて質問できない」なんてことはザラにあります。
その時に困るのは「なぜこんな書き方をしたのか?」「これは書き直しても問題ないのか?」という疑問が生まれた時です。
読み方が分からない and/or コードを追えないのは勉強するなり時間かけて読み込めばなんとかなります。
しかし「なぜその選択をしたのか」は前任者に聞かないとわかりません。
上の (a) と (b) はその点において良いコメントになっています。
(a) は変更不可 (ただしバックスラッシュが読み込まれるようにするのであれば変更可)、(b) は気にせず変更可ということが分かりますよね。
(a) や (b) の例に挙げた意図を、コードだけで表現するのは不可能です (無理やりコードだけで表現しようとすると逆に可読性が下がる)。
「 コメントを書くのは可読性が低いコードしか書けない人間の甘えだよ」
確かに可読性が低いコードしか書けない人が安易にコメントに頼っていては可読性の高いコードを書けるようにならないかもしれませんね。
私も処理の流れが分かりやすくなるように書く訓練として「コメントを書かずにコードを書いてみる」というのは有用だと思います。
ただ仕事で書く場合、伝わるか自信がない部分があればコードを書いた後に積極的にコメントを残すべきです。
個人的な経験として、コメントがあって困ったことよりも、コメントがなくて困ったことの方が100倍多いです。
(すべてのエンジニアがそうだと思うのですが......なぜコメントを毛嫌いする人がいるのか理解できない)
「さらに言えばコメントがメンテされなかった場合、そのコメントは嘘として負債になるだろ」
コメントとやっていることが矛盾しているパターンですね。私も出会ったことがあります。
ただこれ、コメントがなかったらコードを正とするしかないんですよね。
コードの方が間違っていたらどうするのでしょう?
矛盾コメントに悩まされるのと間違ったコードに騙されるの、どちらが良いのかは私には分かりません。
しかし間違ったコメントに騙されても気づいてないので矛盾コメントを嫌うのでしょうね。
それと処理の内容をコメントとして書くのではなくメソッドに分割しているパターンにおいて、メソッド名が嘘ついてた、ということもあります。
低レベルプログラマはコメントだろうがメソッドだろうがメンテしないので、どちらにせよ矛盾したコメントやメソッド名が残るんですよね。
この場合、低レベルプログラマを雇っている会社を変えるか、自分が退職するしかないのではないかと思います。
コメント自体には罪はないので憎まないでほしいです。
まとめ
コメント書かない主義の人たちって自分たちが他の人に迷惑かけてるのを認識していないんですよね。
大体の場合において、後任者が質問したいときには既にいないので不平不満は本人には届かない。
不平不満は届かないので、本人たちは「自分はコードを書ける側の人間だ」と考えているかもしれません。
そう考えると、むしろ彼らが不憫にさえ思えてきます。
まあ一番不憫なのは申し送り事項がないコードを渡された後任者なのですが。