CodeKata
というのは、外国の開発本ではよくおすすめされているサイトです。
システム設計の練習として、非常に評価の高いサイトのようです。
Kata
というのは、空手の「型」のことを指します。
A kata is an exercise in karate where you repeat a form many, many times, making little improvements in each.
雰囲気で読むと…
何度も何度も同じ動き(練習のこと)をすることで、ちょっとずつ改善していく、というところを、Kata
と呼んでいます。
プログラミングでも、型のようなものが必要、ということで、CodeKata
があるようです。
英語サイトなので、適当に訳しながら、問題を解いてみたいと思います。
なお、きつい部分はGoogle翻訳に頼ります。
今回は、Kata01: Supermarket Pricing
を解きます。
問題
スーパーでは、缶詰の豆が、一つ$0.65という、単純な値段で売られていたりします。
ただ、実際には、
- 3つで1ドル(4つ、5つ買った場合はどういう値段?)
- 1ポンドで1.99ドル(4オンス*1買ったらいくら?)
- 2つ買ったら、1つおまけでタダで手に入る(3つめの商品に値段はあるのか?)
というような、複雑な計算が存在します。
これを、どう表現するのが良いしょうか?
考慮すべき事項として、
- 割り切れない?
- 丸め(四捨五入とか)はいつ行われるのか
- 価格決定の証跡をどのようにして残すのか
- コストと費用は同じクラスにするのか
- 在庫の評価はどうするのか(特に、「2つ買い、1つ無料」の場合、商品が100個あったら、それをいくらと評価するか)
なお、この問題はコーディング不要で、モデリングのみで回答します。
価格設定のスキームを処理するのに十分な柔軟性の確保と、同時に一般的に使用可能な金額と価格を表すためのさまざまなモデルを試すこと、が目的とのことです。
考えてみる
今回、いきなりコーディング問題じゃありません。
とにかく、様々な選択肢(モデル)を出すことを求められています。
あらゆる状況に対処できるモデルは難しいので、メリットデメリットを勘案して、モデリングを行います。
問題文、というか、そのあとの内容には、
シャワーの間で考えられるが、全ての選択肢を使いつくすのに数週間のシャワーを要するかもしれない
とある通り、とにかく選択肢を出すことを目的とします。
とはいえ、文章を主とするブログで、モデルを示すのはきつい*2のですが…考えたモデルは文字で表現します。
モデル
これ、レジの中の動きを模せば、それでいいんじゃなかろうか、という気がしてきます。
結局は、データベースの中、というか、普段価格を決めてどうやってそれを登録しておき、どうやってレジから呼び出すか、というところまで考える必要があるのでは…?と思います。
複数個まとめ買い、おまけつき
まず、これらに対応するには、個数と単価を受け取って、計算結果を返すクラスを作成すればよい、と思います。
もちろん、インターフェイスのように、実装を抽象化できる仕組みは使います。
おそらく、商品によっては、10個買うと1つおまけ、のようなパターンもあるので、実装は複数用意します。もしくは、設定で切り替えられるようにします。
購入単位の変換
例でいうところの、1ポンドで1.99ドル、4オンスでいくら?という問題です。
これはまあ、そもそもどういう単位で計測され、金額が算定されるのかについて、決まっている必要はあります。
1オンス単位、4オンス単位というような計測単位が考えられますが、その単価が必要だったり、単位未満の数量をどうするか、という考慮も必要です。
こういう、ポンド←→オンス、のような変換は、変換のクラスを作成すべきと思います。
ポンドを入れたら、ポンド未満だけ別途計算してくれる、みたいな。
これは、「実際の売上はいくらか」を計測するためにも、金額計算が内蔵すべきだと思います。
丸め
どこで、というのは取り決めが必要です。
ただ、これは、「金額に端数が発生するたび」みたいな取り決めになるのでは、と思います。
というか、単価決めるタイミングで、端数が発生しないようにしておいたほうがいいです。こんなの金額計算タイミングまで決定を遅らせないほうがいいです。もしくは、量り売りの総菜みたいに、値段を確定させるか。
在庫評価
単価は仕入値でやるのが良いと思われるので、ここでは割愛します。期末に時価評価されますが、期中の金額(売上原価)は仕入値ベースとなります。
売上数(出庫数)は、売上と同時に払い出すのが良いと思われます。普通そうするでしょうが。
「売上サービス」に、「金額計算」と「在庫」の各ドメインを渡して、処理するようなイメージです。
こうすると、売上によって「売上金額の計算」と「在庫の処理」が行われることが明確になります。
今回は小売業なので、在庫が無いとそもそも売上が発生しませんが、受注生産とかだと、在庫の引き当て等があるので、もうちょっと考える必要があります。
価格決定の証跡
レシート出力用データ、と考えます。
まあこれは、「金額計算」にログ用のインターフェイス渡して処理すればいいんじゃないでしょうか。
こうしておけば、ログ自体の単体テストも可能ですし。
そもそも、一定個数買ったら割引という場合、過程をどう出力するのが正しいのでしょうか…?
本来の単価に個数を乗じ、割り引かれた金額を表示する、でしょうか?
レシートだと、そういうのが多い気がします。
となると、割引額をどこかで管理する必要があるし…なかなか難しくなってきますね。
考え直す
証跡を見ていると、割引額の把握も必要そうなので、考え直します。
個数と単価を受け取って、計算結果を返すクラスを作成
と言っていましたが、計算結果、という部分をもっと考えます。
計算結果に必要なものは、
- 実際に支払ってもらう金額
- 割引額
- 計算の証跡
です。
これらを持つクラスを戻り値にすることとし、計算できない場合(商品が未登録等、何らかのありえない状態)は例外で返してもらうようにする。
例外が発生した場合は、レジ側でなんか通知を出すこととし、それ以外の場合は、上記計算結果をレシートに印字するために、結果オブジェクトをログクラスに渡す。
こんなものですかね。
計算中にログ(というかレシート)を書き込むより、うまくいった場合だけ残すほうがうまいように思います。
注意
これが正解、というわけではないです。
そもそも、私はモデルを図示すらしていませんし。
頭でいろいろ考えて、これでいけるかなぁ、というところまでは考えました。
ここから先は、実際にコーディングして、動かして確認したいところです。
おわりに
こんな感じで、書いていきたいと思っています。
CodeKata
自体は、21問の問題があるので、順次解いていきます。