プログラミング言語には一般的にループ構文があることが多いですが、ループ構文でなるべくネストを浅くする方法について考えてみました。多次元配列をループさせて要素を取得したい場合にループを重ねていくとネストが深くなっていってダサく読みづらいコードになりがちです。
例えば以下のようなオブジェクトがあったとします。
{"companies": [
{
"name": "hoge商事",
"departments": [
{
"name": "営業部",
"groups": [
{
"name": "販売促進グループ",
"members": [
"山田太郎",
"田中二郎"
]
}
]
}
]
}
]}
この中の members
をプログラム内で取得したい場合、素朴な方法としては以下のような感じで書けます。
for (let i = 0; i < companies.length; i++) {
const company = companies[i]
for (let j = 0; j < company.departments.length; j++) {
const department = company.departments[j]
for (let k = 0; k < department.groups.length; k++) {
const group = department.groups[k]
for (let l = 0; l < group.members.length; l++) {
const member = group.members[l]
// memberに関する何がしかの処理
}
}
}
}
しかしこれは今日び正気とは思われないコードです。
やっていることは大したことないのに認知的負荷の高い書き方で、読み手を疲れさせる独りよがりなコードと言えるでしょう。
flatMap や flatten のような関数が組み込まれているプログラミング言語であればそれを活用してネストを浅くすることができます。
const members = companies
.flatMap(c => c.departments)
.flatMap(d => d.groups)
.flatMap(g => g.members)
あとパッと思いつくのは、名は体を表す関数を作ってしまうことでしょうか。定義は省略しますが、以下のように書けたらわかりやすそうです。
const company = companies[i]
const members = membersOf(company)
const groups = groupsOf(company)
const departments = departmentsOf(company)
しかしわざわざ関数を定義するのがめんどくさい場合もあります。定義したは良いけど一回しか使われないしトータルの行数も倍ぐらいに増えた、ということになってしまったら関数化するのが良いことなのかどうかわからなくなります。
オブジェクト指向な言語であればモデルクラスみたいなのを作ってそこにメソッドを生やすのも良いかと思います。
const company = new Company(companies[i])
const members = company.getMembers()
あるいは、本当に全社のメンバーが欲しいのかどうかも考え直してみたほうが良いことが多いです。companiesをドカッと渡して、その中からmemberを全員取得する、というような処理が本当に必要なのか?ということです。ロジックを雑に考えているからそうなっているケースが結構あります。本当は各社の広報メンバーだけが欲しかったりするはずです。
いくつか方法を考えてみましたが、検討する中で以下のようなことを感じました。
- for文とかでゴリゴリ書くのは読み手に不親切なのでやめたほうがいい
- なるべく自然言語に近い表現ができるコードにすると読みやすさが向上する。読みやすいコードは自然とネストが浅くなってることが多い
- コードが読みやすいとロジックの拡張がしやすいので高度な機能を実装しやすくなると思われる
あとはパフォーマンスがどうなのかという話があると思いますが、経験上こういった書き方の問題でパフォーマンスが大幅に犠牲になることはないです。普通にfor文で書いたほうが速い場合は確かにありますが、それが問題になったときに初めて書き直せば良いかなと思います。そしてRustならゼロコスト抽象化のパワーによりパフォーマンスの問題すら考えなくて良いということになります。Rustを使っていきましょう。