tyamaguc07's hatenablog

考えたり調べたりしたことを書いていく。

kotlinの Iterable<T>.distinct() を眺めて気付いた、「実態としての可変」と「型としての不変」

list.toSet().sizelist.distinct().size は同じ結果になる。

この違いは何であろうか?
調べてみるとStack overflowに記事があった。

stackoverflow.com

distinct() は内部的に toSet() しているとのことだ。
ほほーと思い実装を見に行った。

public fun <T> Iterable<T>.distinct(): List<T> {
    return this.toMutableSet().toList()
}

toSet() ではなく toMutableSet() ではあるが、確かにSetに変換することで重複を除去しているようだ。
その後に、toList() をしているので、重複を除外したいのであれば distinct() を使うより toSet() したほうがパフォーマンスが良さそうだ。

もしかしたら、 toList() で効率的な処理がされているのかも?と思い、 toList() の実装を見ると、面白いことに気がついた。*1

戻り値に List<T> を期待しているところで、 MutableList<T> を返しているのだ。

public fun <T> Iterable<T>.toList(): List<T> {
    if (this is Collection) {
        return when (size) {
            0 -> emptyList()
            1 -> listOf(if (this is List) get(0) else iterator().next())
            else -> this.toMutableList()
        }
    }
    return this.toMutableList().optimizeReadOnlyList()
}

MutableList<T>List<T> を継承しているので、この返し方ができるのは分かる。
ただ、不変であることはどのように表現されているのかが気になったのでo1に教えてもらった。

(タイトルの通りだが)結論、インスタンスの実態としては可変リストであり、型としては不変として表現されているので、私達は不変リストとして利用することが出来ているとのことだった。

言語レベルの実装を見ると、このような気付きが得られることが良い😆

*1:面白くて、効率的な処理のことは頭から吹っ飛んだ

コーディングするときに大切にしていること(その1)

自分がコーディングをしているときに大切にしていることはいくつもある。
この記事では "今日"、実際に大切にしたことを書く。

変数名は型を類推可能なものにする

Kotlinを書いているので、類推できなかったとしてもIDEが教えてくれる。
ただ、能動的に教えてくれるタイミングはコーディングしているときである。
つまり、リーディングしているときには能動的には教えてくれない。 もちろん、カーソルを当てると型を出してくれはするが、それは変数の型が類推できなくなったときの行動であり、喜ばしい状況ではない。
したがって、類推可能であることは大切だと考えてコーディングをしている。

変数定義は使う直前に

リーディング時の認知負荷を上げる要因として、よくあるのが変数定義されたがしばらく登場しない状況である。
これは、「定義された」=「すぐに使われるはずである」という期待が生み出す認知負荷ではあるが、いまどきのコーディングルールでは必ず書かれているものなので、期待することは避けられない。

したがって、新規に書かれるコードは期待に答えてくれるものが多い気がする。
逆に、コードを変更するタイミングで期待にそぐ****わない状況が起きてしまうことがあるような印象を持っている。

変更するときには、特に意識したい。

結果的に整合する(楽な)実装があったとしても、ドメインの表現ができる実装を行う

例えば、以下のようなドメインの表現があるとする。

  • ある条件が適用されるとモデルの状態がAからBになる。
  • Bの状態のモデルを使って、ロジックを動かすと必ず結果は一定になる。

このとき、「モデルの状態をAからBに変更する」実装コストが高いとしよう。
そして、「ロジックの中でモデルに対する適用条件を見て、一定となる結果を導く」実装コストが低いとする。

このとき、どちらの実装を選ぶだろうか?
私は、実装コストが高い方を選ぶようにしている。 *1

これは以下のように考えているからである。

  1. 結果的に整合する状況が担保されていない可能性がある
    • 条件が変わったときにロジックが破綻する可能性がある
    • 破綻しなかったとしても、条件が変わることで必要な情報が変化し、本来ロジックに必要のないはずだった状態が増えてしまい、本質的ではない依存が発生する
  2. ドメインを理解し実装を見ると、いたずらに認知負荷を上げてしまうこと
    • あるべき箇所に実装されていないので、なぜ動いているのか分からなくなることが予想できる
    • あるべきではない箇所にあるロジックが、どこから発生したものなのか推測できない

今日は記事かけて一安心。
カジュアルにかけるネタを今後も探っていきたい。

*1:一応弁明を書いておくが、可能な限りだ。実装コストが10倍高く、時間をかけることができないような状況では、しぶしぶ低いコストを選ぶことはある。

コアドメインとサブドメインに対する私の考え

「ドメイン駆動設計をはじめよう」で、コアドメインとサブドメインとは何であるのかが記載されているが、私の解釈とは異なるものであった。

今回の記事では、私はどのように考えているのかを書いてみる。

私のコアドメインとサブドメインに対する考え

ものすごくシンプルで、「コア」とは視点のある場所であり、視点が変わることで「コア」も変わると考えている。

例えば、境界づけられたコンテキストによって分かれるドメインAとドメインBがあるとする。

ドメインAを視点とすると、ドメインAがコアドメインであり、ドメインBは サブドメインであると考えている。 逆にドメインBを視点とすると、ドメインBがコアドメインであり、ドメインAはサブドメインである。

極端な話、両方がコアドメインと言えると考えている。
しかし、それら区別してコミュニケーションする必要はある。なぜなら、区別しない場合に、必要以上に境界の先にあるドメインのことを知ってしまう可能性があるからだ。


初めてエリック・エヴァンス本を読んだときから10年は経っており、独自解釈が強いかもなと書きながら思ってしまった。 あらためてエリック・エヴァンス本を読んだうえで、「ドメイン駆動設計をはじめよう」との差をまとめて別の記事にしてみようと考えている。

「ドメイン駆動設計をはじめよう」の1章に感じた難解さの正体

(この記事は、 「ドメイン駆動設計をはじめよう」を読んで、ヘンリーの事業活動を分析してみる(その②: 分析 with GPT o1) の続きです。)

あらためて1章を読み直してみて

結論、当初より難解に感じることはなかった。
これは、前回の記事によりヘンリーの業務領域の中核・一般的・補完的な業務の解像度があがったことで、私自身の視座が固定化しやすくなったことが強く影響したのだろうと考えている。(仮説通りである)

良かった一安心と思い、先の章を読み進めていくと、「そうはならないのだが。」という気持ちが生まれてしまった。

10章 10.2 区切られた文脈 で明確になった文脈の違い

10.2 区切られた文脈で以下の図が示され、前述した気持ちを生み出した。

「ドメイン駆動設計をはじめよう」10章 10.2 区切られた文脈 より引用

この図は、サービスの境界を決めるときの考え方として提示されているものである。
したがって、書かれている「中核」「一般」「補完」はおそらく一つ一つが、ソフトウェアで表現されているものと考えている。

さて、Henryのサービスを同様の図として表現すると、「中核」がでてこない結果になると考えている。
なぜなら、Henryのソフトウェアが提要している機能あるいは価値は、医療機関からすると「一般的な業務領域」に対応するものだからだ。
したがって「中核」がないにも関わらずサービスを提供しているヘンリーという事業者は、本書の論理とは整合できていない存在になってしまう。

ソフトウェアを「活用する」世界と「前提とする」世界の違い

本書の主たる視座は、ソフトウェアを「活用して」更なる競合優位性を生み出す事業であるように考えた。
したがって、引用した図のように「中核」は必ず存在し、「一般」「補完」は場合によっては存在する状況になるのだろう。

しかし、前述の通りヘンリーはそうではない。

ヘンリーは一般的な業務領域に対するソフトウェアをサービスを提供する会社である。 このとき、ソフトウェアは「活用する」ものではなく、「前提とする」ものである。

本書は、「活用する」世界の視座で書かれていることが多いため、「前提とする」世界で生きている人には、たまに難解に感じる状況を生んでしまうと考えている。

ドメインエキスパートとの会話に出てくるモデルの目的を理解する大切さ

昨日、ドメインエキスパートの使う表現を可能な限りコードに落とし込む大切さ という記事を書いたが、今日はドメインエキスパートとの対話の中にでてくるモデルをコードに落とし込んだ結果、難しい実装になってしまった事例と、何故それが起きたのかを書く。

樹形図というモデル

毎度のことだが前提知識から記載する。

  • 患者は医療保険と重度医療と呼ばれる公費制度(以下、重度医療)を持っている
    • 医療保険は医療費の3割を患者が負担する
    • 重度医療は患者の負担額上限を500円にする公費制度である*1*2
  • 医療費が10,000円の場合、負担額・給付額は以下のようになる。
    • 医療保険の給付額は7000円
      • 患者が10,000円のうち3割負担するので、その残り
    • 患者の負担額は500円
      • 重度医療によって患者の負担額は上限の500円で止まる
    • 重度医療の給付額は2,500円
      • 患者の負担額を3,000円から500円にするため、その差額が給付額となる

理解いただけたであろうか。
文字で見るとやはり難解である。

これを樹形図という形で簡略化したものが以下となる。

医療保険・公費による負担額計算事例の樹形図モデル

文字だけで見るより、難解さは減ったのではないか。
制度の事例になれると樹形図で見ると意図を汲み取りやすいため、負担額・給付額の具体的な計算事例として、公開されることが多い。

この図を見たエンジニアは樹形図の概念をモデルとして実装にも落とし込めると考えるのではないだろうか。 私もそう思った。チームメンバーもそう思った。実装に落とし込んだ。

結論から言うと、そのアプローチで実装されたコードは難しいものになってしまった。

より複雑な事例を表現した樹形図

より複雑な事例を表現した樹形図

この樹形図がどんな事例であるのかは省略するが、見切れた「解説」があることから図の難解さを伝えてくれるのではないか。 なお、その解説自体もなかなか難解なものである。

<解説を見てみたい人はクリック>

説明が難しいだけでモデリングとしては使えるのではないか?とも思わなかっただろうか。
実は、ツリー構造のモデルとしてあまり一般的ではない依存が発生している。

赤丸で囲んだ数字は、ルートノードである総医療費を点数をもとして算出された数字となっている。
よくあるツリー構造は「親から子へ」という近接したレベル間の影響・継承を前提とするものであり、これに反している状況である。

このような親以外への依存はいろいろな制度に対応するためにたくさん発生している状況であった。
結果、そのような暗黙の情報がバケツリレーされるような状況を生み出していた。
これにより、情報がどこでどう使われているのか分かりづらいという難解さを生んでいた。

ドメインエキスパートとの会話で利用するモデルではあったが

樹形図は、ドメインエキスパートとの対話で利用する具体的なモデルではある。
しかし、実装にそのまま落とし込めるモデルではなかった。

このギャップは、モデルの目的が違うことから発生していると考えている。
樹形図というモデルは計算結果を理解しやすい形で表現するモデルであったのだろう。制度の理解には非常に役に立つ。
一方、私達が求めていたのは負担額の計算のロジックのモデルであったのだ。

目的を理解する難しさを作ったのが、樹形図というモデルでも、簡単な制度であれば計算のロジックのモデルとして利用可能なものであったことだと考えている。
モデルの目的を理解出来ていないうちに、使えるものであると判断して進んだ結果、コードの難しさを生み出してしまった。

これからは、あるモデルが使えるかもと思ったときに、一度立ち止まり、モデルの目的を正しく理解しているのかを考えるようにしていきたい。

*1:宮崎の公費制度事例。似たような公費制度は各都道府県に存在するが、制度内容が異なる

*2:厳密には月の負担額上限を500円にする制度

ドメインエキスパートの使う表現を可能な限りコードに落とし込む大切さ

この記事は株式会社ヘンリー Advent Calendar 2024の18日目の記事です。 18日 26:20に書き上げました!セーフ!!!!

ドメインエキスパートとの会話で難しい概念やロジックがでてくることがある。
それらは一見、意味がわからないものであったりするが、ドメインを理解することで適切な表現であることを理解する瞬間がある。

最近それを感じた瞬間があったので、紹介してみようと思う。

前提知識の共有

まず、具体例を理解するための前提知識を共有させてほしい。

  • 患者は医療保険によって、医療費の1割〜3割を負担することで医療を受けることができる。
  • 患者は公費と呼ばれる制度によって、医療費の負担を更に減らすことができる
    • たとえば、難病医療費助成と呼ばれる公費(以下、難病公費)を持っていると医療費の2割まで負担するだけで済むようになる*1
  • また、患者が負担しなかった金額は、医療保険または公費によって給付された金額という扱いとなる

ちょっと難しいかもしれないので、具体的な事例を2つ紹介する。

事例 1) 3割負担の医療保険と難病公費を持っている患者が医療費が10,000円の医療サービスを受けた場合
  • 医療保険によって発生する負担額は3,000円(10,000円 の 3割)となる
  • 難病公費によって発生する負担額は2,000円(10,000円 の2割)となる
  • このとき
    • 患者の負担額は*2は2,000円となる
    • 医療保険の給付額は7,000円となる
    • 難病公費の給付額は1,000円となる
      • 1,000円 = 医療保険によって発生する負担額(3,000円) - 難病によって発生する負担額(2,000円)
事例 2) 1割負担の医療保険と難病公費を持っている患者が医療費が10,000円の医療サービスを受けた場合
  • 医療保険によって発生する負担額は1,000円(10,000円 の 1割)となる
  • 難病公費によって発生する負担額は2,000円(10,000円 の 2割)となる
  • このとき
    • 患者の負担額はは1,000円となる
    • 医療保険の給付額は9,000円となる
    • 難病公費の給付額は0円となる
      • 難病公費は患者に2,000円まで患者に負担を求めるが、医療保険によってそれを下回る負担額となっているた0円となる

(難解な前提知識で申し訳ない。)

Henryでの実装

負担額と給付額という概念はシステム的に重要で算出し永続化されている。
上で紹介した内容を見ても負担額・給付額の計算ロジックは複雑だなと思われたかもしれないが、他にも影響を与える要素が多数あり、計算ロジックはよりさらに難解となる。

難解なロジックの認知コストを下げるモチベーションは高い。
仮に公費による給付が発生しないのであれば、公費によるロジックをスキップすることが可能であり、全体の見通しを上げることができる。それが認知コストを下げることに繋がる。

さて、あなたは、公費による給付が発生するかどうかを、どのような判定式として書くだろうか?

事例をシンプルに判定式に落とすと以下のような式を書こうと思うのではないか。

val is給付あり = 医療保険による負担額 > 公費による負担額

一方、Henryのプロダクトコードとして以下のような判定式が存在している。

val is給付あり = 医療保険の負担割合 > 公費の負担割合

同じ結果になることを理解できるだろうか?
これは、以下の式から共通の項である「医療費」を打ち消した式である。

val is給付あり = 医療費 * 医療保険の負担割合 > 医療費 * 公費の負担割合

さらに、項を整理すると、1つ目の式と同様のものとなる。

val 医療保険による負担額 = 医療費 * 医療保険の負担割合
val 公費による負担額 = 医療費 * 公費の負担割合
val is給付あり = 医療保険による負担額 > 公費による負担額

最初の式と同じ結果となる判定式であることは理解いただけたと思う。

難解に感じる式が使われている理由

どちらの式が理解しやすいと感じただろうか?

この記事を読んだ方は、前者の式が理解しやすいと感じたのではないかと思う。
それは、おそらく、前提知識の共有として説明した内容では負担割合の比較という表現はないからだ。

では、なぜHenryでは難解と感じる式が使われているのか。
それは、ドメインエキスパートとの対話ででてくる表現が後者であったからだと考えている。

ドメインエキスパートの使う表現を可能な限りコードに落とし込む大切さ

複雑なドメインのプロダクトを作るにあたって、ドメインエキスパートとの対話が欠かせないものである。
対話の中ででてくる表現を理解することが、ドメインの理解につながる。
そして、その理解をコードに落とし込むからこそ、ドメインエキスパートの表現と、実装の表現の差を減らすことが可能になっていくのである。
一見難解なコードであったとしても、それをドメインエキスパートとの対話のきっかけとして利用し、ドメインの理解を進めるほうが、より良い状況を生み出すであろう。

逆に、そうしない場合、表現の差が増えてしまう。
この場合、ドメインを理解するきっかけは減ってしまう可能性もある。
更には、異なる事象の説明をされているように感じてしまうかもしれない。
結果、ドメイン理解を進める足かせになってしまうかもしれない。

この記事を読んでくれたあなたの中で、ドメインエキスパートの使う表現を可能な限りコードに落とし込む大切さが少しでも上がれば幸いである。

*1:負担割合が減ること以外に、負担額は限度額までになるという仕組みもあるが、本記事では割愛

*2:患者が会計時に支払う金額

アウトプットにもっとリアクションしていこうと思った

営業日にブログを書きはじめて10日目。

会社の1on1で、毎日ブログ書いてますねーと反応いただいてありがたいなと思った。
また、XでもいいねやRTをしていただくこともあり、嬉しい気持ちになっている。ありがとうございます。

あらためてアウトプットに反応してもらえることは良い体験であるなと実感した。

一方、私自身はあまり他の人のアウトプットに反応してこなかった人間である。
反応するときは、学びがあるや、他の人も見てほしいというような気持ちが発生したときであった。

前述のとおり、私の毎日のアウトプットに対して反応いただける体験をしたことで、もっと気軽に反応しても良いんだなと考えるようになった。
そう、アウトプットしただけで偉いのである。そこに反応して、アウトプットを見たことを伝えたい。
そして、またアウトプットしてみる気持ちを少しでも醸成していく手助けができればいいと考えている。

そうしていくうちに、私に起きたような変化が他の人にも発生し、アウトプットすること自体がモチベートされやすい世界になっていくと良いなと思う。