🍜

ラーメンの構造に学ぶ、コード設計 - そこに汎用性はあるんか (≠Rahmen編)

2023/02/04に公開
6

プログラムを使ってある仕様を実現するとき、多くの場合、そこに"唯一の答"はありません。
同じ仕様、機能を実現するコードにも多様性があります。
プログラミングにおいてしばしば問題になるのが、「その様々なコードのうち、どのコードを選んで実装するか?」ということです。
とりあえず機能が実現されるという点においてはどのコードを選んでも同じであっても、その後の保守性や拡張性などにおいて、自分がどんなコードを選んで書くか という事は重要です。
今の時点では正しく動作しているコードであっても、可読性や拡張性などの観点でクソコード、悪いコードなどと揶揄される場面がしばしば見受けられます。クソコードというのはかなり強い言葉で、あまり良い言葉だとは思わないですが、その言葉を発する人からすると、どうしてもそう言いたくなるような問題があるのでしょう。

ところで、同じ労力で悪いコードを避けて実装できるのであれば、そうしたいですよね。他の人から悪いコードと言われてしまうようなコードは、できれば選びたくありません。

そこで、この記事ではコードを選ぶ方法=コード設計について、汎用性という重要な観点の説明と、汎用性のあるプログラムのメリット/デメリット、コード設計に関する問題提起、なぜ"全て"をDomain Primitiveにすべきでないのか、といった事についてラーメンの構造を通して 学んでいきます。この記事では、悪いコードを生み出す設計=悪い設計の例として、Domain Primitiveを多用しすぎた設計を取り扱います。

※以下、tweetの引用などがありますが、題材として取り上げているだけで、投稿者を非難するような意図はないため誤解の無いようにお願いします。素敵な題材を投稿されたMinoDrivenさんに感謝します。私としても様々な事柄への理解が深まりました。ありがとうございました。
※冒頭の文章をすこし書き換えました。本文の内容は特に変わっていません。

題材(ラーメンの構造)

以下の画像は、「ラーメントッピングをシステムにより全自動化する場合どんな概念構造になるかを考えたもの」です。

※出典:https://twitter.com/MinoDriven/status/1621425477528457217

「ラーメントッピングをシステムにより全自動化する」という意味を私は正確に捉えきれなかったのですが、この構造を見ていると、次のような考え・疑問が浮かびました。

  • 金額計算とかは別でトッピングマシーンを作るというニュアンスで解釈する
    • 仮に各味付けの独自の具材があったら、それはスープに含まれると仮定する
    • 替え玉はこのシステムの対象外とする
    • トッピングの組み合わせや量で金額変わると思うが、それも扱わないと仮定する(トッピングマシーンなので)
    • 太さや硬さは厳密にはトッピングというより調理方法だが、構造に含まれているため、トッピングとして扱う
    • 太さと硬さ(≒茹で時間)は独立ではないのでは?茹で時間分岐は太さと硬さの組み合わせにするのかな?
  • こんな具体的な要素にして、複数店舗に導入できるのかな?

全体的に、私はこの構造に賛成できず、また効率的な処理実装ができるようにも思えなかったので、どのような考え方をするとこの構造が出てくるのだろうか?という事をしばらく考えていました。
その結果、私の考えの前提にある重要な要素に気づきました。その重要な要素とは、汎用性 です。
実際、最後の疑問「こんな具体的な要素にして、複数店舗に導入できるのかな?」というのは、まさしく汎用性を問うものです。この汎用性という概念を通して構造や私の考察を振り返ると、私の違和感を深く説明できたのです。以下、詳しく述べていきます。

汎用性のないプログラム、汎用性のあるプログラム

まず、汎用性のないプログラムと汎用性のあるプログラムの差について、少し考えていきます。
いま、あなたは友達Aさんと一緒にいて、Aさんに12345679×27はいくつ?と質問をされたとします。
pythonがインストールされたパソコンの前に座っているあなたは、退屈な計算をpythonにやらせるため、次のようなプログラムを作ってpythonで実行しました。

print(f'12345679 * 27 = {12345679 * 27}')

12345679 * 27 = 333333333と画面に表示されたので、333333333と答えました。

このようなプログラムは、12345679 * 27を計算するというめちゃくちゃ具体的な目的が決まっていて、他の計算をするような余地は全くありません。これは、目的が明確な汎用性のないプログラムです。

さて、友達Aさんがこんどは12345679×45はいくつ?と質問をしてきたとします。
あなたは、さっきの27を45に書き直すかもしれません。
もう一人、横にいた友達Bさんは違っていて、12345679×27の計算をするときに次のようなプログラムを作っていました。

def mult(x, y):
    print(f'{x} * {y} = {x * y}')


mult(*[int(x) for x in input().split(' ')])

Bさんは、このプログラムを書き換えずに動かして、12345679 45と入力しました。Bさんは汎用的なプログラムを作っていたので、プログラムを書き換えるのではなくて入力データを書き換える ことで目的を達成しました。
このように、プログラムには汎用性という概念があり、汎用性のないプログラムはより具体的な問題のみを解ける一方、汎用性のあるプログラムは複数の類似の問題を解く事ができます。
もっとも、Bさんのプログラムも掛け算の問題にしか対応していないので、あくまでも最初のプログラムよりは汎用性があるという程度問題になります。汎用性というのは、ゼロ/イチの絶対的な概念ではなく、相対的な概念になっています。

ここで扱われているような12345679×27の計算のような簡単な話であれば、それが汎用的であるか否かはあまり大きな問題ではありません。一度だけ計算するのであれば、汎用性の無い計算の方が手早く楽です。一般に、汎用性がある方がよいのかというと一概にそうではなく、解きたい問題・解決したい物事に対して適した汎用性の度合いがあります。コード設計においては、適した汎用性を意図的に選択して実装をする 事が重要です。そのことを詳しく見ていきましょう。

ラーメンの構造における、麺の硬さと汎用性

ここで、冒頭のラーメンの構造の例における、麺の硬さについて考察してみます。
麺の硬さとしては、バリカタ、普通、柔らかめの3種類がクラスとして 定義されていました。この
3種類がクラスとして定義されているという事は、先程の例でいえば汎用性のないプログラムの書き方と似ていて、麺の硬さのバリエーションを増やそうとした場合には、プログラムの修正が必要になるという事を意味します。
例えば、「粉落とし」と呼ばれるバリカタの上の硬さ(というかほとんど茹でてない)を実現するには、プログラムの修正が必要になってしまいます。
これらの麺の硬さが、単に茹で時間などを変更するだけで実現できるものであるとすれば、以下のような設計の方が汎用性があります。

  • 硬さインターフェイスは単に硬さクラスとする
  • 硬さクラスには名前と茹で時間をもたせる
  • 硬さクラスのインスタンスとして、バリカタ、普通、柔らかめを用意する
    • 必要なら粉落としを追加する
  • 硬さクラスのインスタンスの内容は、管理画面などでユーザーが設定できるようにする

一般論として、麺の茹でる時間のバリエーションを増やしたり、あるいは味の調整や季節変化等で茹で時間を変更したりといった事は容易に考えられるので、必ずプログラマの修正が必要なコード設計よりは、プログラマが修正しなくても済むコード設計の方が良いシチュエーションが多いのではないかと思います。

なお、これは、トッピングクラスの場合には必ずしも同じ事は言えないと思っています。トッピング自動化がどんな事を示すのかわかりませんが、仮にロボットが卵や海苔やネギを自動でトッピングしてくれるようなイメージとすると、トッピングする為には固有の作り込みが必要になるかもしれません。もし固有の作り込みが必要であれば、個別にトッピングするメソッドを実装する作りが自然になるので、この構造にも一理あります。
(ただ、リスト構造のようなものを渡した結果について、要素のメソッドを呼ぶことで"見栄えよく"配膳してくれるのか?という疑問はあるので、本当にこの構造がベストかはよくわかりません...全体の状況を考えながら配置する方が自然だと思うので、卵や海苔にメソッドをもたせるのはあまり適切でないような気もします。)

汎用性のあるプログラムの"落とし穴"

一方で、常に汎用性のあるプログラムを書けばよい、という事でもありません。この例の場合、トッピングマシーンの管理者が入力を間違えると、柔らかめなのに10秒しか麺を茹でない、といった"バグ"が発生する可能性があります。一般に、データ変更が容易であることは、それによって発生するエラーの数とトレードオフになる事があります。
とはいえ、麺の茹で時間であれば事前に管理者の人が十分に確認できるので、事実上のデメリットはほぼ無いと思います。ただ、これが原発の燃料棒の制御プログラムであったとすれば、それを容易に変更できる事は致命的な問題になり得ます。
一般に、「汎用性をどれぐらいもたせるか」という事は設計において本質的で重要です。よく考えて設計をする必要があります。一例として、例えば開発者のリソースが十分にあるならばちょっとした仕様の修正の都度プログラマが修正を行っても構わないでしょうが、開発者のリソースが限られているならば一般の利用者だけで対応が収まるようになっている方が適しているでしょう。

汎用性のないプログラムを書くのは楽しい場合がある

ところで、実装をする作業に目を向けると、汎用性のないプログラムを書くのは楽しい場合があります。
例えば、細かい部分まで考えたプログラムの構造がピチッとはまって実装していく感覚は、精密なプラモデルのパーツを組み上げていく感覚と少し似ているかもしれません。一つ一つのパーツは単純でわかりやすく、それを組み上げて全体が出来上がっていくという感覚は楽しいです。このような細かいクラスそれ自体はテストもしやすく、単体実装レベルでの作業それ自体は楽しい可能性があります。つまり、何も考えていなければ、こういった設計を好むプログラマが居るということです。あるいは、汎用性のあるプログラムを狙って書くのはそこそこ難しく、汎用性を高めようとして手が止まるぐらいならば、手が止まらない汎用性のない実装を進めてしまうというパターンもあります。
ただ、そのような設計になっていると、非プログラマのユーザーは仕様の"調整"をすることができず、全体最適の観点では最適になっていない可能性 があります。開発者は、そのような事になってしまわないように、細心の注意を払う必要があります。

ここで、さらに問題になり得るのは、開発会社とシステム利用会社が異なる場合には、このような仕様で実装をすることは開発会社の短期的な利益になってしまう という事です。言葉を選ばずに極端な言い方をすると、不必要な仕事を開発会社が生み出してしまいます。しかも、このような構造上の問題は、表面的な仕様には表れなかったり、事前に非開発者がチェックする事が難しかったりするため、利用会社は開発会社の言いなりにならざるを得ない場合があります。
この問題は根が深く、言い方を間違えれば開発者からすると「適切な実装・設計を心がけて細かく実装したのに、なぜ非効率であるかのように言われないといけないのだ」といった話に発展しかねないので、危険な問題でもあります。個人的には、私自身が開発者でもあるので、開発者が正しい倫理と全体最適の思考を以って業務にあたる ということぐらいしかないかなあと思っています。

全てをDomain Primitiveにする"教義"が汎用性を破壊する

もう一つ、汎用性のないプログラムを書く方向に圧力が加わる場合があります。それは、Domain Primitiveを極端に・教義的に適用する場合 です。
例えば、あるラーメン店における麺の硬さ、バリカタ、普通、柔らかめというのはドメイン知識であると言えますが、現状の業務を正確にトレースしてこれらをプログラム上で表現してしまうと、システムとしては柔軟性が損なわれるという皮肉な結果になります。というのは、バリカタ、普通、柔らかめという概念の本質は茹で時間などの具体的な長さのデータであって、プログラム上の概念として「バリカタ」「普通」「柔らかめ」として区別されるべきものではないからです。
型を定義することでプログラミングにおける意図せぬエラー を少なくする事が可能ですが、それは同時にプログラム上明確に定義されていない概念を扱えなくする という自由度を少なくするという事でもあります。
ドメイン知識をただちにプログラム上の構造として表現することは適切ではなく、システムの設計はデータ運用も含めた総合的なものであるべきです。極端な事を言えば、システムでは定義されていないが運用上でだけ呼び表される概念というのも十分にあり得ます。もちろん、それが運用上でだけ定義されていて頻繁にエラーが発生するような場合には、システムを再設計して型などの実装に還元することに意味がありますが、常に全ての概念をプログラム上で定義すればよい訳ではありません。教義的にDomain Primitiveの考え方を全てのものに適用するのではなく、メリットデメリットを検討して、これはDomain Primitiveとして定義する、これは定義しない、という事を判断する事こそが設計なのだと思います。

むすび

この記事をまとめると、以下のような内容になるかと思います。

  • コード設計とは、特定の仕様を実現する上での具体的な実装を選択する方法と思う事ができる
  • コード設計には汎用性がある/汎用性がないという概念がある
    • 汎用性のありなしはゼロ/イチの絶対的な概念ではなく相対的な概念である
  • 汎用性のありなしには、それぞれメリットデメリットがある
  • 汎用性のないコードの方が具体的で、汎用性のあるコードの方が抽象的になる
    • 無意識的に具体的なコードを求めてしまい、深く意図せずに汎用性のないコードが生まれる場合がある
  • 汎用性のないコードのデメリットは客観的に説明しにくい場合がある
  • Domain Primitiveを濫用すると、汎用性のないコードが生まれるような圧力が生じる
  • メリット・デメリットを踏まえて、意図的に汎用性のないコードを選ぶか、汎用性のあるコードを選ぶか、それをコントロールすることこそがコード設計(の一部)である
    • むやみにDomain Primitiveを使えばよいという事ではない

私の中でも、まだ汎用性をどう扱うべきか言語化できてはいませんが、コード設計における重要な概念だと思うので、問題提起的に汎用性について述べました。ぜひ、コード設計における汎用性について考えてみてください。

この話題と関連する設計の記事:

https://zenn.dev/339/articles/e3c174fdcc083e

↑の記事でも、一部の概念をプログラムで具体的に表現しすぎる事についての弊害を指摘していましたが、汎用性を通して自然に説明できるようになりました。

https://zenn.dev/339/articles/ecc4986473ca88

↑一部、汎用性を保持し続けたシステム開発の難しさについて言及しています。

Discussion

さざんかぬふさざんかぬふ

この記事は、

  • ドメイン知識の実現方法としてDomain Primitive(Value Objectの一種)しかないと問題が生じる
  • ある種の区分値については、プログラムでマスタ管理できた方が汎用性のある設計になる
    • Domain Primitiveとして作り込むのではなくて、プログラムは具体的な値の入れ物だけを用意して、値の管理はユーザーに任せる

という軸をもうちょっと強調した方がよかったかも?
まあいずれにしても、Domain Primitiveを多用する作りになぜ違和感があるのか、という事をはっきり示せたので、それはよかったなと思います。
つまり、型として具体的に定義していたものを、ユーザー定義のマスタ管理に移行しようとすると、途端にその型たちは技術的負債として立ちはだかることになる、ということです。作り込めばよい訳ではない。

ドメイン知識が十分に無いかも?という謙虚さがあれば、いきなり作り込みすぎることを避けて、まずはもっと最低限の簡素な形で実装するのがよいのかな、と。

flathill404flathill404

具体的すぎる実装、抽象的すぎる実装どちらも困るケースありますよね…

どこを落し所にするのかはドメインへの理解度はもちろん、ビジネスをどうしていきたいのかという目線でよーく考えないと…!

そこを怠っているのに、プログラムの綺麗さばかり重視して型安全だーと言っても本末転倒な結果に…

面白い記事をありがとうございました!

philomagiphilomagi

本記事の内容とTwitterの
「マスタ管理可能な設計にしてください」
「(プログラムは)エクセルほどではないがエクセルみたいなもの」
といった言及を拝見して、議論の方向性としては『CODE COMPLETE 第2版 上』18章「テーブル駆動方式」のアプローチが近いと感じました(あるいは、そもそもそれを念頭を置かれていたりするでしょうか)。
https://atmarkit.itmedia.co.jp/fdotnet/bookpreview/codecomp2nd_index/codecomp2nd.html
https://twitter.com/sasanquaneuf_/status/1622038707602870273
https://twitter.com/sasanquaneuf_/status/1624048322720702465

本記事の論旨に同意で、ドメイン知識を全て(Domain Primitive や Value Objectといった方法で)プログラムコード上で表現してしまうと、それを直接閲覧・操作できるプログラマにとっては明瞭明快になる一方で、「ソフトウェアを使ってユーザーができること」の範囲が著しく制限されてしまいますね。

すると、プログラマには「わかりやすい」「きれい」と映る一方で、実運用上はユーザーが自分の都合に合わせてソフトウェアをカスタマイズ(変更)できず、結果、個別のケースに対応するために都度都度デプロイしなくてはならないという事態になりえるなと(私は実際なりました[1])。

ドメイン知識を全てプログラムコードに押し込んで硬直させるのではなく、ユーザーが自身のために自ら変更する余地を作る[2]ための考え方として、また、現にそれを可能とする形式(=コードに依らない形式)でドメインを表現するための手法として、「テーブル駆動方式」あるいは「マスタ管理可能な設計」「エクセルのようなソフトウェア」といったアプローチは有力そうだなと本記事を読んで思いました。

脚注
  1. OCPはある程度達成されていたので、大構造こそ大崩れはせずバリエーションを追加さえすればよく「(プログラマ視点に閉じた、コードの)変更容易性」はある程度担保されていたものの、実運用上の「(ユーザーが自ら行う)変更容易性」は損なわれたまま(=「汎用性のない」ソフトウェア)でした ↩︎

  2. 個人的には「ユーザーに明け渡す」という表現を好んで使います ↩︎

さざんかぬふさざんかぬふ

コメントありがとうございます!

テーブル駆動方式について、もともとテーブル駆動方式という名前を念頭に置いていたわけではありませんでしたが、本質的には同じような事だと思います。
細かい違いを挙げるとすれば、例えばCode Completeでは保険料率テーブルのためのデータを宣言する例においてEnumとテーブルを定義していますが、私が記事で提案したのは「麺の硬さクラス」をただのテーブルではなくてクラスとして定義しているというところぐらいでしょうか。データを処理するためのメソッド/関数をどこに書くべきかは状況によって異なるので、内容によってはクラスを定義する必要も無いかなかと思います。

エクセル云々については、少し補足をすると、

  • エクセル自体がテーブル構造をプログラムの中で扱えるようになっている
  • 狭義のテーブル構造に限らず、エクセルには汎用性がある

という2点があり、私はどちらかというとこの後者の方を考えていたので、具体的にテーブル構造という事よりも、汎用性の方が焦点でした。この後者を実現する場合の一つの要点として、エクセルにおけるセルの構造をどのようにプログラミングするか?という事があります。
この事について、頭の体操としてよいという主旨の以下のようなツイートがあった(はず)のですが、すぐに見つけられなかったので、関連するツイートを貼ります。

https://twitter.com/sugimoto_kei/status/1478729866115977216

https://twitter.com/sugimoto_kei/status/1439797838741852167

https://twitter.com/sugimoto_kei/status/1574270448988090369

https://twitter.com/sugimoto_kei/status/1553243336655249409

ちなみに、これらのツイートで批判されている「モデリングを抽象化と捉えるのはスジが悪い」というあたりが、引用いただいた私のツイートの元になった発表資料へのストレートな批判になっていたりします。(時系列的には、ツイートの方が先ですが!)

私のツイートの元になった発表資料)

https://speakerdeck.com/minodriven/purpose-abstraction-design

これは、プログラムした結果について、汎用性が無いことを良いと考えるか、汎用性があることを良いと考えるか、いずれかという嗜好性/指向性/志向性の差によるものと思っています。
つまり、とにかく単一目的のためにだけ動作するような機械をみっちりミチミチと作り上げるか、高度な汎用性を有したソフトウェアを作り上げるか、みたいな目指すものの差です。
アソビのないプログラムを書くか、アソビのあるプログラムを書くか、みたいな表現もできるかもしれません。
プログラマがとにかく汎用性の無いように書きつつ、改修が発生したら都度手を動かすようにすれば、コードは管理しやすくなりますが、一方で汎用性が低くなるので、汎用性が必要になるような改修に対してはプログラマコストがかかり続けるビジネス構造になります。(ベンダーロックインを含めれば、これは開発者にとっては"良いこと"でもあったりするので、複雑です。)
一方、汎用性があるように書けば、都度エンジニアが手を動かすような事は少なくなる一方で、プログラム自体がある意味で難しくなり、それをメンテナンスできるプログラマが限られるようになります。
良いコード/悪いコードの本が広く受け入れられる事には一定の意味があって、つまり多くのプログラマは汎用性の無いコードを書きたいのではないか、という事を思うようになりました。汎用性の無いコードの方が"わかりやすい"し、コードを書くことそれ自体はパズル的な要素もあるからです。
個人的には、もっと難しいパズル、つまりいかにして汎用性を担保できるプログラムを提供するかという問題の方に取り組める人が増えてほしくて、またそれこそが真のプログラミング(?)ではないかと私は思っていますが、それは多分少数派なんだろうと思います。

philomagiphilomagi

大変丁寧かつ詳細な返信、ありがとうございます!

テーブル駆動方式について、もともとテーブル駆動方式という名前を念頭に置いていたわけではありませんでしたが、本質的には同じような事だと思います。
細かい違いを挙げるとすれば、例えばCode Completeでは保険料率テーブルのためのデータを宣言する例においてEnumとテーブルを定義していますが、私が記事で提案したのは「麺の硬さクラス」をただのテーブルではなくてクラスとして定義しているというところぐらいでしょうか。データを処理するためのメソッド/関数をどこに書くべきかは状況によって異なるので、内容によってはクラスを定義する必要も無いかなかと思います。

そうですね、CODE COMPLETEの例とはいくらか差異はあるものの、本記事の説明もテーブル駆動方式とその本質を同じくするものだと思います。

テーブル駆動方式の場合、各種のバリエーションについて識別子とそれに紐づく値のセットという形式に変換できるよう構造化すること、すなわちバリエーションをコードではなくデータ構造によって吸収することが肝要であるという理解です。データ構造によってバリエーションが吸収されているので、データ構造に対応するクラス自体にはバリエーションが不要となり、その結果としてenumで実装することもできるしRDBなどで表現することもできるようになるというイメージです。
「マスタ管理可能な設計」というアプローチでも、マスタ管理を可能にするには「識別子とそれに紐づく値のセット」という形式へ整理することが要求されると思います。ですから、CODE COMPELETEとの違いはそれを実現する実装としてenumを使うのか(レコードに対するマッピングとしての)クラスを使うのか、はたまたそれ以外を使うのかという程度のもので、根本にある発想は同じなのだと思います(現実にマスタ管理するためにはenumを使うわけにはいかないでしょうが、あくまでマスタ管理「可能」な設計ですので)。


エクセル云々については、少し補足をすると、

  • エクセル自体がテーブル構造をプログラムの中で扱えるようになっている
  • 狭義のテーブル構造に限らず、エクセルには汎用性がある

という2点があり、私はどちらかというとこの後者の方を考えていたので、具体的にテーブル構造という事よりも、汎用性の方が焦点でした。

なるほどです、わざわざ引用まで記載いただきありがとうございます。この意味での「エクセル性」について、アラン・ケイのDynabook構想およびそれに含まれるパーソナル・コンピューティングのアイデアをつい連想してしまいます。
https://ja.wikipedia.org/wiki/ダイナブック

Dynabook構想およびパーソナル・コンピューティング、「エクセル性」、「マスタ管理可能な設計」、これら三者が実は同じことを語っている、とは考えていません。ただ、「いかにユーザーへ(ソフトウェア上における)自由を提供するか」という観点[1]は、この三者において共通したものではないかと考えています。
この観点から、Dynabookおよびパーソナル・コンピューティングは全面的な自由を、「エクセル性」は用途・意味付けにおける自由を、「マスタ管理可能な設計」はバリエーションの追加・拡張における自由を、それぞれユーザーに提供している(or しようとしている)と整理できないかなと、ふと思いました。

この観点が有効だとしたら、本記事で書かれた「汎用性」については、以下のように言い換えられたりするでしょうか。

  • 汎用性がある = ユーザーが自由にできる余地(アソビ)があるソフトウェア

  • 汎用性がない = ユーザーが自由にできる余地が無いソフトウェア

  • Aより汎用性がある = Aよりもユーザーが自由にできる余地が広い/多い

  • Aより汎用性がない = Aよりもユーザーが自由にできる余地が狭い/少ない

ユーザーが自由にできる領域が広ければ、多少の不都合があってもユーザー自身がその不都合を変更してしまえば事足ります。しかしその領域が狭ければ、ユーザーにできることはソフトウェアを開発者に変更してもらうか、不都合を飲み込んでソフトウェアに合わせるか、いずれも無理なら使うのを諦めるか、となってしまうと思います。
反面、ユーザーの自由な領域が広がれば広がるほどユーザーの「ミス」によってソフトウェアそのものをおかしくしてしまう危険が広がり、一方でその領域が狭ければ狭いほどそのような「ミス」はそもそも不可能になってきます。
この傾向は、本記事で語られている汎用性のそれとも合致するように思います。そしてこうした「自由」は、恐らく抽象化によって自然発生するものではなく、「自由を与えよう」という積極的な働きかけによって初めて生じるもので、そのために抽象化というテクニックが用いられるのだろうと思います[2]

(仮定に仮定を重ねてしまいますが)だとすると、「なぜ汎用性のないプログラムを書いてしまうのか」、および「汎用性のあるプログラムとないプログラム、どちらにするべきか」といったテーマについて、以下のように答えることもできないかと考えました。

  • なぜ汎用性のないプログラムを書いてしまうのか
    • そのプログラムにおいて「ユーザーに自由度を与える」という観点を持っていない or 不要と考えているから
  • 汎用性のあるプログラムとないプログラム、どちらにするべきか
    • ユーザー毎に都合が異なり、多種多様なケースがある(ありうる)箇所は、汎用性をもたせる(自由を与える)
    • ユーザーによらず一貫して従うべきルールであり、都合によって変更してはならない箇所は、汎用性をもたせない(自由を与えない)

ここまでから、「汎用性のあるプログラム」と「ユーザーに自由を与えるプログラム」、一方の観点では響かない嗜好性/指向性/志向性を持つ人であってももう一方の観点からは響かせられたら良いなあ、などと願望を抱いたりしました。これは、「いかにして汎用性を担保できるプログラムを提供するかという問題の方に取り組める人が増えてほしくて、またそれこそが真のプログラミング(?)ではないか」という嗜好性/指向性/志向性に私も共感するがゆえの願望です。

脚注
  1. 返信いただいたコメント中の「アソビ」などは、まさしくユーザーに対して部分的にせよ自由が提供されたからこそ生じたものではないかと思います(類似表現として、私は「余白を残す」という表現を好んで使っていました)。 ↩︎

  2. この観点から、エクセルのお話からの「モデリングは抽象化ではない」という主張にも私は同意します。 ↩︎

さざんかぬふさざんかぬふ

コメントありがとうございます!

汎用性について、そうですね、直接的に狭義のユーザーが自由に設定できるかどうかは別として、設定をする人も含めた広義のユーザーに自由を提供するという考え方はできると思います。
偶発的に意図しない使われ方をする・汎用性が生まれてしまう場合もゼロではないと思いますが、プログラムの設計として「ここは可変/自由にする」ということを意図していなければ、有意義な自由/汎用性は提供されにくいと思います。
必ずしも汎用性の話ではないですが、例えば車の設計で、ブレーキが踏み込む方向にアソビを持っているのは意図されたアソビでしょうが、単にグラついて横方向への"自由度"をもってしまうのは設計誤りという事だと思います。
そのような意味で、設計上「どこを意図的に可変にするか」は重要であると思います。

それをいろんな人に考えてもらう為には、

  • まずプログラムにおいて汎用性という概念が存在すること

を知るのが設計に反映するための第一歩かな?と思っています。実際、良い設計についての本を見る時、コードの量を少なくするとか実装を単純にするという意味でテーブル駆動方式などの説明はありますが、「プログラム自体が汎用性を持つべきか否か」という汎用性の文脈ではあまり深く説明をされていない気がしています。
良い設計の本、特にコードコンストラクション/コード設計のようなものを扱う本の場合、「その他の条件が同一であれば」バグが発生しにくい・プログラマが読みやすいコードの方が良いコードとなるので、自然と汎用性が無いコードの方が良いコードであるとなりがちです。Domain Primitiveの徹底などはその最たる例と思います。それはそれで、改修を全くしないor全ての調整で必ずエンジニアコストを発生させるなら正しいと思いますが、実際には運用を経て改修や拡張を現場で対応したくなる場合が多々あります。
そう考えると、先程の「その他の条件が同一であれば」という仮定が成立しない場面が浮かび上がってきます。つまり、"現在"表に出ている要件の裏に、今後の調整や追加などが隠れていて、それも踏まえれば汎用性の高いコードを選んだ方が適切であるという場面が出てきます。そうした場面が実際に存在するのだということを、具体例を以って伝えることが大切かなと思っていて、それでこの記事を書きました。

ところで、私自身は直接そのような教育を受けた訳ではなく、自分でてきとうにコードを書いていた時から既に汎用性を意識していました。これは、これまでに書いたプログラムの用途や質によって影響を受けた部分があると思っています。
例えば、ゲームプログラムの場合、ユーザーが意図的に可変にするという概念は少ないものが多いです。オリジナルマップを作るゲームや、RPGを作るツールみたいなものもありますが、大半のゲームにおいてはユーザーはデザインされたバランスで遊ぶだけです。
また、ある種の組み込み機器においても、ユーザーが設定を変更管理するということはあまり無いかもしれません。
一方、私が最初にまともに人に使ってもらうプログラムを書いたのは中学生の時でしたが、これは塾の先生が問題を読み込んで生徒に使わせるタイプの学習ツール(英語の並び替え作文問題)だったので、二種類のユーザー(設定する人、使う人)がいました。また、ゲームに関しても、ユーザーがマップを作って遊ぶタイプのゲームを遊んでいて、プログラムの中でさらにデザインをするという概念が当たり前にありました。ある種のメタ性を持つ仕組みに若い頃から触れていて、そのような経験が私の考え方を作っているように思います。

さらには、もっと広く個人の性格や、プログラミングに限らない経験も影響するものと思います。例えば、システムをどう作るのが"正解"だと思っているか。私は、自分ひとりの力では80点のシステムまでしか作れないと思っていて、その上を狙っていくには必ず実際に使う人の考えが必要で、私自身は正解を持っていないと思っています。(あるいは、もし仮に自分が100点を取れるなら、他の人の力を加えれば120点を超えられるという言い方になるのでしょうか。)
モノとしてのプログラミングの部分は仮に私が100%作るとしても、それによって実現されるシステムとしてベストなあり方については、自分ひとりでは正解を得られないという前提を持っています。他の人がいないとシステムは完成しないので、他の人の力はどこにどう入るのか、みたいな事を常に考えて設計しているように思います。

また、幸か不幸か、私はコードを書いていて叱られるという経験をした事が多分一度もないです。最終的に作っていたものがバグっていて、後の工程や納品後などにそれによってお叱りを受けるという事は何度かありましたが、単純にコードを書く工程の段階で叱られたことは多分一度もないと思います。
よく「楽しそうにコードを書く」と言われるのですが、そういう意味でコードを書くのは難しいと思うことはあっても本当に辛い/嫌な事をやるという気持ちにはならないので、とにかく汎用性を少なくして自分の身を守りたい、あるいは自分の設計を否定した人を見返すために完璧なコードを書きたい、みたいな気持ちは全然無いなあと思います。
そういう性格や経験の違いで、そもそも汎用性のないコードを目指したい/汎用性のあるコードを目指したいという違いが生まれている気がします。