技術的負債を抱えたレガシーコード。変なメソッド名と入り組んだロジック、リファクタリングするならどちらが先?(後編)
今回は「技術的負債を抱えたレガシーコード。変なメソッド名と入り組んだロジック、リファクタリングするならどちらが先?(後編)」についてご紹介します。
関連ワード (出会、最初、複雑等) についても参考にしながら、ぜひ本記事について議論していってくださいね。
本記事は、Publickey様で掲載されている内容を参考にしておりますので、より詳しく内容を知りたい方は、ページ下の元記事リンクより参照ください。
ソフトウェアの品質をテーマに研究をしている名古屋大学 森崎研究室は、ソフトウェアの技術的負債をなんらかの形で数値化する手法の研究の一環として、コードの読みにくさの原因となる要因などを分析した研究結果を発表するイベントをオンラインで開催しました。
この記事ではそのダイジェストを紹介します。記事は前編と後編の2つに分かれています。今お読みの記事は後編です。
森崎氏による補足説明
前編では、グループA(命名的問題)より、グループB(構造的問題)の方が正答率が大きいということ。一方でグループA(命名的問題)よりグループB(構造的問題)の方が読みにくさを感じた、という点に統計的に有意な差があったことが発表されました。
発表の後、オンラインイベントの参加者からの質問について森崎氏と和田氏による補足説明が行われました。
森崎氏 グループAとグループBの参加者が本当に均質なのか、というご質問ですが、これはアンケートを信じるという形で分けていますので、人によってはアンケートの回答が謙虚な方もいらっしゃるでしょうし、アグレッシブな方もいらっしゃるかもしれないので、その点においては本当に均等かと問われると難しいところがあります。
ただ、この分野での研究ではこれ以上の適切な方法はないと言われていまして、今回の研究でも割と適正にグループ分けをしていると考えています。
「命名的問題」と「構造的問題」という名前についてですが、命名的問題は、英語で「Linguistic Antipatterns」と呼ばれる研究があって、それに対応する日本語としてここでは命名的問題と呼んでます。このLinguistic Antipatternsを分類している研究があり、その分類でなるべく多くの分類を含むようにコードを変更しました。
「構造的問題」は、コードが読み取りにくくなっていて、本当ならもう少しシンプルなコードで書けるものを、そう書いていないものをそう呼んでいます。
これらの名称自体は、もしかしたら別の用語や定義があるかもしれませんが、いずれにせよコードスメル(Code Smell)などでも指摘されるような代表的なものになります。
和田卓人氏による考察
続いて、タワーズ・クエスト株式会社 和田卓人氏による考察が紹介されました。
和田氏 今回の研究において統計的に有意な差があったことが結果として出てきたことには、とても価値があったと思っています。
しかもその結果が面白いんですよね。
グループB、つまり命名に問題はないが構造的に問題を抱えているコードを読解した方々の方が、読みにくさを感じたにもかかわらず、正答率が高かった。ここが今回の研究の最も面白いところです。
ソフトウェアエンジニアリングをやっていて一番面白い瞬間は、直感に反するような、しかし統計的には明らかな結果が出たときかなと思っています。
その意味で、読みにくさ、構造的な読みにくさがその読解精度を低下させているとは限らないという結果は非常に面白いです。
構造的に問題がなさそうだけど命名が誤っているコードに関して、そもそも命名が誤っていることに気づかずに、あるいは表現が難しいですが、命名をそのまま信頼してコードを読み進めていくことによって、結果的に読みやすいと感じたけれども、正答率は低い、というような結果が導かれた、というようなことも考えられますよね。
「技術的負債」などと言われたりするものを構造的な問題と命名的な問題に分けたときに、どちらがより深刻かというところを、ある程度人数を集めて、統計的にどんな差が出るかを実際に調べてみたわけです。
それを実際に現場の知見として還元できて、いわゆる産学連携できたらいいなというようなところがあって私にお声がけいただき、今回お手伝いをさせていただいたというような次第です。
研究のコンセプトとか問いとか、そのあたりのお手伝いをさせていただき、それがこういう結果で出たのは大変面白いなと思っています。
それによって、プログラマの現場で命名的問題と構造的問題を目にしたときに、どちらに対してより問題を感じるか、どちらから着手するか、そういったところの判断材料に今後していただければと思ってますし、研究自体はさらに深めて続けていくことが大事だなと思っています。
「命名的問題」があるとコードが信じられなくなる
私が現場でこの2つに出会ったら、やはり黄色信号が灯るのは「命名的問題」の方なんですよね。
なぜかというと、コードに「命名的問題」があると、もう目の前のコードが信じられなくなるので、より集中力を高めてコードを読まなくてはならなくなるという感覚があるんです。
日々コードを書いている中で、コードの一部を関数に抽出したりとか、メソッドにしたりクラスにしたり、いわゆる共通化や抽象化というものを行うときに、それはなんのためにやるかというと、省力化のために行うんですよね。
抽象化することで、コードの詳細まで読みにいかなくとも、名前を見て、中身を信じるという、コードの書き手とコードの読み手のあいだの一種の信頼関係によって認知コストを下げる。あるいは脳のワーキングメモリの消費を減らしていく。という関係が成立するんです。
けれど、名前とコードの中味や、やっていることが違う、という場面を発見したときには、脳内に黄色信号が灯るというか、これはコードの書き手との信頼関係が成立しないな、というモードになるんですよね。
抽象化されていればその名前の先を読みに行かなくてもいい、という信頼関係のもとにコードリーディングしていたものから、信頼せずに、その先で実際には何をやってるかを読みに行くモードにチェンジしなきゃいけなくなるんです。
モードチェンジするとコードを読む時間もかかりますし、認知負荷も上がり、だいぶワーキングメモリを消費することになるんですね。
統計的に有意な差が出てきたのは興味深い
ですので、私は命名的問題の方がまずいんじゃないかな、とは思っていたんですけれど、はっきりと統計的に有意な差(読解精度には命名的問題の方が悪影響を与える)が出てきたというのは、面白い結果だなと思います。
そして、構造的問題の方がコードの読みにくさを感じたにも関わらず正答率が高いというのは、おそらく今回の調査に参加される方々を最初JJUG(日本Javaユーザーグループ)で募ったので、だいぶ強者が集まってきたからかもしれません。
こうした方々が日々、業務の現場で読んでるコードの複雑な構造的問題に比べれば、今回はそのサイズの面から見て屁でもなく読めてしまう。長年の格闘の経験から、この程度の構造的な複雑さならすいすいと読めてしまう、というようなところもあったのかなと思います。
ですので、もっと多様なプログラマの方に読んでもらったら、どういった結果が出てきたかなっていうのは興味があるところです。
Decoratorパターンの知識があるかどうかで読みやすさも変わるというのはその通りで、Decoratorパターンが分かっていればさっさと読めますし、分からなければ、Decoratorパターンはマトリョーシカのようになっているので、どこでこのコードが重なっているんだ、というのが分からないと読めないですよね。
それが読み取れないとコードの読解は難しかっただろうなというふうに思いますし、さらに言えば、このパターン知識があって、パターンぽい名前がついてるにも関わらず、実際のコードでやってることが違うとさらに混乱が生じるので、それも含めて、やはり命名の問題のダメージの深さが印象深くなった、というような調査だなというふうに思いました。
「良かれ」と思ってレガシーコードに問題が蓄積されていく
森崎氏から、どのようにしてレガシーコードに問題が蓄積されていくのか、と問われた和田氏は、レガシーコードを触るプログラマがDiff(差分)を小さくしようとしたことが、問題の蓄積の一要因だと指摘します。
和田氏 年季の入ったレガシーコードの場合だと、1人の人がコード全体を書いているわけではなくて、いろんな人がコードに関与した結果、レガシーなコードになっている。だから、特定の誰かのコードが悪いというものではなくなっているんです。
しかも現場のレガシーコードはどちらかというと、良かれと思ってコードが継ぎ足されていく。元々構造的問題があったり、あるいは命名的問題があったとしても、それが修正されずに継ぎ足されていく、という性質を持っています。
なぜ「良かれと思って」となるかというと、プルリクエストごとにコーディングのスタイルが違うと、レビュワーにとっては認知負荷になるわけですよね。
だから、基本的には良かれと思って既存コードとコーディングスタイルを合わせようとする。あるいは、これは善し悪しがある、まあ悪い方が多いんですけれど、機能追加の際などにレビュワーの負担を下げようとしてコードのDiffを小さくしようとするんです。
本当は、このインデントの構造を変えた方がいいんだけど、それをするとDiffが大きくなるから止めよう、とか、本当はメソッドに抽出したほうがいいんだけど、それだとDiffが大きくなるからこのif文を追加で、みたいな感じでDiffを小さくしようとする。
(GitHub上などで)Diffでコードレビューするようになったことで、Diffの小ささがよしとされてしまう。これをやや俯瞰で見ると、構造的複雑さとか命名的な悪さというのが、じわじわと累積されていく形になるんですね。
そういうのが何年も積み重なると、現場のレガシーコードが出来上がる。
多くの人が良かれと思いつつ問題がじわじわと累積していくことで、いつの間にか認知負荷の高いコードという形になるので手ごわい、という感じですね。
慣れていないからこそ指摘できることもある
和田氏 プロジェクトに参加したばかりの方とか、あるいは新人プログラマの方によく言ってるのは、コードが読みにくいとか分かりにくい、ということははっきり言った方がいいよ、ということですね。
プロジェクト等に参加してる期間が長くなってくると、良くも悪くもコードに慣れてしまって読めるようになってしまうんですよね。
そうすると様々な保守性の問題に対して気づきにくくなってしまいがちなので、慣れていない時期だからこそ指摘できることがたくさんあるよ、とよく言います。
命名だけで抽象化の問題はなんとかできる?
森崎氏 いまオンラインミーティングのチャットでこんな質問をいただきました「命名だけで抽象化の問題は何とかなるもんなんでしょうか。ある程度大きなコードを書いてると、処理の中間の状態でうまく命名できないケースがあるかと思います。その際に、命名はそこまでこだわらず、他のドキュメントで詳細に概念を書いて、そこへの参照をコード内のコメントで貼っておくという方法も取れるかなと思ってるんですが、いかがでしょうか?」
和田氏 これは、なくはないと思います。
書籍「A Philosophy of Software Design」のジョン先生と「Clean Code アジャイルソフトウェア達人の技」のボブおじさんの2人は仲が悪いというか、バチバチやってるんですけど、それですね。
Clean Codeのボブおじさんは、メソッドの命名に苦戦するのはそもそもそのメソッドの責務が大きすぎるからなので、メソッドを分けろと言います。メソッド名でそのメソッドが何をしているかをはっきり示せるぐらい、メソッド自体は短い状態を保ちましょう、どんどんリファクタリングして関数やメソッドで分けてていきましょう、と言っています。
一方、A Philosophy of Software Designのジョン先生は、そういうことをやっていると結果的に認知負荷が高くなってしまうし、視線移動が多くなってしまう。たくさんのメソッドを読まないと何をやってるかわからなくなってしまうので、結果認知負荷が高くなってしまう。だから、ドキュメントをちゃんと書こうと、そしてパブリックインターフェースをちゃんと設計しようと言っています。
この2つのスタイルはどちらも極端だと思うので、自分たちのスタイルとして、これらの中庸のスタイルとしてどのあたりに持ってくるかがポイントだと思います。
ただ、細分化するとたくさんコードを読む必要が出てしまうという意見は、僕はどちらかというと間違っていると思ってます。その細分化したメソッドや関数の先を読みに行かなくていいために細分化していると思ってるので、だからその処理に名前を付けて切り出している。
読みにいかなくてよくなることで、たくさんのメソッドを読む必要がなくなるというのが抽象化の大事なところだと思っています。
関数名は日本語で書いてもいい
和田氏 英語的に正しいメソッドを見つけても日本人に通じない問題がある、というコメントもありました。これは「あるある」ですね。
さらに言えば、英語にもイギリス綴りとアメリカ綴りがあったり、なんかいろいろあって英語的に正しい命名をしてもあまり通用しなかったり、とかもあるので、例えばテストコードのテスト関数名とかはもう日本語でバンバン書いちゃいましょうねと、現場で言っています。
テストコードの関数名って必然的に長くなりがちなんですよね。
これを英語的に正しくしようとするとすごい頑張った感じになってしまうので、それをやるぐらいだったら日本語で書きましょう、とよく言ったりしています。
森崎氏 ありがとうございます。ここで一区切り付けたいと思います。
関連記事
- 品質を犠牲にすることでソフトウェア開発のスピードは上がるのか? 和田卓人氏による 「質とスピード」(前編)。デブサミ2020
- 東証がSREによるレジリエンス向上に挑む理由。過去のシステム障害から何を学んだのか?(前編) ソフトウェア品質シンポジウム2022
- グーグルはコードの品質向上のため「バグ予測アルゴリズム」を採用している
- 4年後までにソフトウェアテストの70%を生成AIが作り、コードの品質は向上するようになるとの予測、IDC