概要
新人プログラマ応援に乗じ、業務アプリ開発者としてどういう事を考えながらプログラムを「開発」するものなのか、割り勘アプリを例にしたメモ。「割り勘」などというごく特定のイチ機能を「ドメイン」等と表現するのは少々乱暴かもしれませんが、「イメージの付きやすい例で説明をしたい」という点にフォーカスする意図で読んでいただけたらうれしいです。
割り勘アプリ
会計アプリ、人事給与アプリ、などという領域は大きすぎるので、「割り勘アプリ」を考える。利用シーンとしては
- ランチなどで一緒の店に行く
- 送別会や出産祝いなどでプレゼントを共同購入する
際に、一人あたりいくら支払えば良いのかを計算してくれるものとします。
- 例えば1000円の花束を3人で買ったら、300円はらうか400円払うかして、お釣りは幹事に任せる。そのユースケースによってはこの余りの金額は幹事の「幹事代」というお駄賃になる金額かもしれないし、余り次第でお釣りを再分配する金額かもしれない。
具体的にプログラムを考えてみる。
実装
支払金額と、割り勘に参加する人数を入力する。
Ver.1
# 入力: 金額、わる人数
amount = int(input("金額: "))
ninzu = int(input("人数: "))
q, mod = divmod(amount, ninzu)
winner_amount = q+mod
print(f'Winner will pay:{winner_amount}, other pay:{q}')
入門者的プログラムを基にします。
例えば divmod
なんていう関数を使いプログラムをシンプルにするのは良い(= ここはプログラムを学習する時に評価されるポイント)、と思うのだが、1000円の花束を3人で買いましょうという時に winner will pay:334, other pay:333
なんて言われても、そんな端数は現実的に使いません(= ここが仕事でプログラムを書く時のポイント)。
ここで初めて「実際の利用者」がどう使うかという想定の必要が出てくる。つまり「割り勘アプリ は、金額を人数で割るだけのプログラムではない」ということ。
参考: Python組み込み関数〜divmod~ 割り算の商と余りを同時に取得しよう
Ver.2
少し工夫してみる。
# 入力: 金額、わる人数
import math
amount = int(input("金額: "))
ninzu = int(input("人数: "))
q = math.ceil(amount / ninzu / 100) * 100
amari = amount - q * (ninzu)
print(f'100円単位では、一人あたり{q} 円で、余りは {amari} 円です。')
割り勘電卓を作ってみよう という記事を参考にしつつ説明。ここでは100円単位での切り上げが想定された動きになった。
例えば合計金額3500円のランチで、1人あたり100円単位なら3人で割ったら答えは1200円、余り100円。この「100円単位」は、実際の利用想定をした機能強化。こういうことがドメイン(事業領域)といった界隈で、とりわけ実際の会計アプリや人事給与アプリなどでは法律知識だったりを含めてプログラムに反映される。
考えてみると更に大きな買い物の割り勘など、もう少し、柔軟な単位で端数を扱っても良いですよね。
Ver.3
そんな機能強化のアイディアを反映するには、どうしよう。
# 入力: 金額、わる人数、単位
import math
amount = int(input("金額: "))
ninzu = int(input("人数: "))
unit = int(input("単位(例:100円→100、1000円→1000 等): "))
q = math.ceil(amount / ninzu / unit) * unit
amari = amount - q * (ninzu)
print(f'{unit} 円単位では、一人あたり{q} 円で、余りは {amari} 円です。')
今度のプログラムでは「単位」を決められるようになった。これで将来 28000円の飲み会を6人でという時には、1人あたり5000円でなどというリアルな金額も出せる。
めでたしめでたし?
一件落着と見えるが多くはそうならない。Ver.2 から Ver.3 において、入力しなければいけない引数が1つ増えてしまいました。ユーザーによっては、100円単位の切り上げ計算でちょうど良かったのに、わざわざ 100
を入力する手間が増えてしまったということ。
すると困ったことに Ver.2 を愛用していたユーザーが、もしかしたら Ver.3 を使いたくなくなる、Ver.2 を使い続けたいと考えるかもしれません。そうなるとこのアプリに不具合修正をしなければとなったときに Ver.2系, Ver.3系 両方を保守しなければならないことになります。
Ver.3.1
ならどうする? ひとつの答えです。
import math
amount = int(input("金額: "))
ninzu = int(input("人数: "))
unit = int(input("単位(例:100円→100、1000円→1000 等。デフォルトは100): ") or "100")
q = math.ceil(amount / ninzu / unit) * unit
amari = amount - q * (ninzu)
print(f'{unit} 円単位では、一人あたり{q} 円で、余りは {amari} 円です。')
3つ目の引数に下位Versionとの互換を考えたデフォルト値を設定しました。
実際には更に
- お釣りを再分配したい
- 送金アプリと連携したい
- しかし現金払いを考慮したい
- お店とやり取りをしたい
- クーポン券などによる割引を考慮したい
- 支払い催促をしたい
- 人に応じて支払い金額の傾斜をかけたい
...等など、もちろんセキュリティやパフォーマンスや同時アクセスや(以下略)
利用者からは様々な要望が寄せられることが想定されます。もちろんその中には不具合といわれてしまう指摘もあります。それらをどういった優先順位で、どういった期間でリリースしていくか、大規模な複数人の開発チームで調整しながら、かつ他部門や時に利用者そのものと対話しながら進めていくのが実際の、開発のお仕事である。
非常にざっくりしすぎた説明ですが、そんな様子が伝わるとうれしいです。
まとめ
自分が採用活動や研修に関わったりすると、いつもその「ドメイン(事業領域)」というものの説明に四苦八苦するなという思いがあります。それは自分の学習のためのプログラムが 100人中100人が同じ答えとなる明快な答えを出す ものであるとしたら、仕事で書くプログラムは 利用者のゴールを達成する というもしかしたら そのゴールの定義からしていかなければならない ものだからということによるのかもしれません。
時としてユーザー同士の要件がコンフリクトすることとも闘いながらプログラムとして完成させなければならない局面があります(それが上述の Ver.3 辺りの例え話)。筆者はこういうものを書いてそのゴールを定義する作業に面白みを感じつつ、開発を続けています。
ということで
に頷きつつ書きました。すこしでも実開発とはどういうものか、その「ややこしそうだけど楽しそう」なところが伝わればさいわいです。