なぜそのクソコードは理解しづらいのか
はじめに
ネストが深いコードや、一つの関数で様々なことを行っているコードなど、よくクソコードとけなされがちなコードがあると思います。
そのようなコードがなぜ理解しづらくなっているのか、自分なりに思ったことのまとめです。
主にプログラマー脳を読んで思ったことです。
なぜそのコードが理解できないのか
例えば以下のようなコードがあったとします。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
class Program
{
static void Main(string[] args)
{
var a = new List<string[]>();
var aa = new List<string[]>();
if (File.Exists("inputfile1.csv"))
{
if (File.Exists("inputfile2.csv"))
{
using (var r1 = new StreamReader("inputfile1.csv"))
{
using (var r2 = new StreamReader("inputfile2.csv"))
{
while (!r1.EndOfStream)
{
string data = r1.ReadLine();
string[] values = data.Split(',');
if (values[1] == "hoge")
{
aa.Add(values);
}
}
while (!r2.EndOfStream)
{
string data = r2.ReadLine();
string[] values = data.Split(',');
a.Add(values);
}
}
}
using (var writer = new StreamWriter("file.csv"))
{
foreach (string[] values in a)
{
string line = string.Join(",", values);
writer.WriteLine(line);
foreach (string[] values2 in aa)
{
string line2 = string.Join(",", values2);
writer.WriteLine(line);
}
}
}
}
}
}
}
このコードを上から順に読もうと思うと、以下のことを意識する必要があるかと思います。
(いい感じの汚いコードが思いつかず、ChatGPT君に書いてもらったコードを更にわかりにくくいじったものです)
- a,aaの二つのリストがある
- 2つのファイルを読み込もうとしている
- 1つ目のファイルの方は2列目のデータが特定のときだけ(?)保持している
- 2つ目のファイルは全て読み取っている
- 別のファイルに出力しようとしている
- a(だから多分1つ目のファイル)の内容を順に書き出している
- その中でもう一つのファイルの内容を都度出力している
- よく見るとところどころ使用する変数を間違えている?
突然ですが、人間の脳ではこの用に一時的な内容を覚えておく短期記憶というものがあります。
この短期記憶の容量は人によって差があるものの、せいぜい7±2個の情報までしか保持しきれないといわれています。
一般的にマジカルナンバーと呼ばれているものです。
この情報を踏まえて先ほどのコードを読んだときのことを振り返ると、簡単に書き出しただけでも8つの事を認識しようとしています。
少し簡潔に書いているので、実際になにも知らずに読もうと思うともう少し多くの事柄を意識しながら読む必要があるのではと思います。
このように読みずらいコードの特徴の1つとして、一度に複数のことを意識して読み進めないと理解できない構造になっています。
複数のことを意識しようとすると、先ほどのマジカルナンバーの問題が発生し、序盤に記憶した内容がなにであったかわからなくなりします。
今回の例はC#で作成したので、型で幾分か推測できる部分もありますが、動的型付け言語で型ヒントなどがない場合、各変数がどういった型のデータが入っているかも記憶する必要がでてくるかもしれません。
そうなると更に記憶しないといけないことが多くなってしまいます。
(この文章も途中で短期記憶の話を挟んでいて認知負荷が高い気がしました)
じゃあどうするか
実際にこのコードで実現したいことは、以下に要約できます。
- csvファイルを読み込み、2列目のデータが特定のデータの場合のみ保持
- もう1つcsvファイルを読み込み、こちらは全て内容を保持
- 1つ目のcsvファイルの1データ毎に2つ目のcsvファイルのデータを挿入したデータを作成
- 作成したデータを別のcsvに出力する
今回の場合だと、このやりたい大項目毎の処理を行えるメソッドを作成します。
そして各メソッド名は各やりたいことを表すような適切な名前をつけます。
そしてそれをmainで順次呼び出せば、まず全体で何がしたいのかがすぐ読み取れるようになります。
詳細を確認したければ、個々のメソッド単体を確認すればよいです。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
class Program
{
static void Main(string[] args)
{
var onlyHogeData = ReadHogeDataOnly("inputfile1.csv");
var allData = ReadAllData("inputfile2.csv");
var outputData = AddAllDataEachHogeData(onlyHogeData, allData);
OutputFile(outputData);
}
}
このようにやっている事柄毎に処理を分割し、役割を表す適切な名前をつけてあげることや、変数名もどういったデータが入っているのかがわかるような名前を付けてあげる必要があります。
そうして少しでも一度に認識しないといけないことを減らし、読む人の負荷を下げる必要があります。
終わりに
プログラマー脳を読んで色々自身が理解できないコードなどなぜ理解できないのか、またはどうすればよかったのかなど納得のいくことが多くとても勉強になりました。
面白いので是非読みましょう。
またfukabori.fmの第102回であった認知負荷に関して、課題内在性負荷と課題外在性負荷の話はとても面白かったのでおすすめです。
Discussion