Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
一緒に使うことが多い値は別クラスにしよう(Data Clumps)/data_clumps_i...
Search
kirimaru
August 24, 2022
Technology
0
610
一緒に使うことが多い値は別クラスにしよう(Data Clumps)/data_clumps_is_useful
【LT登壇7名決定!】リーダブルコード LT会 - vol.4 #readablelt
https://rakus.connpass.com/event/253650/
kirimaru
August 24, 2022
Tweet
Share
More Decks by kirimaru
See All by kirimaru
DDD(ドメイン駆動設計)を知らない人に知ったつもりさせる/Introduce_DDD_to_unfamiliar_individuals
hirotokirimaru
0
210
例示! Spring Bootで作られた REST APIのテストコード/ Testing-Example-for-a-REST-API-created-with-Spring-Boot
hirotokirimaru
2
1.6k
Backlogが好きな話。/i_like_backlog
hirotokirimaru
0
100
私が好きなポートアンドアダプターを紹介する/I-like-hexagonal-architecture.pdf
hirotokirimaru
1
730
名付けのためにクラス図を元に会話しよう/Let's-use-class-diagram-to-communicate-with-client
hirotokirimaru
0
570
Code Smellsの Primitive Obsession に気を付けて設計する/Designing-with-Code-Smells-Primitive-Obsession
hirotokirimaru
1
3.1k
FCCを推す/My favorite software architecture is FCC
hirotokirimaru
0
170
我々はなぜオブジェクト指向やDDD等のアーキテクチャを学ぶのか/Why_we_learn_ObjectOriented_and_DDD_Architecture
hirotokirimaru
1
990
SLAPを覚えてリファクタリングに方針を/we learn slap for refactoring
hirotokirimaru
1
370
Other Decks in Technology
See All in Technology
Apple/Google/Amazonの決済システムの違いを踏まえた定期購読課金システムの構築 / abema-billing-system
cyberagentdevelopers
PRO
1
220
オニオンアーキテクチャで実現した 本質課題を解決する インフラ移行の実例
hryushm
14
3k
omakaseしないための.rubocop.yml のつくりかた / How to Build Your .rubocop.yml to Avoid Omakase #kaigionrails
linkers_tech
3
730
Aurora_BlueGreenDeploymentsやってみた
tsukasa_ishimaru
1
120
visionOSでの空間表現実装とImmersive Video表示について / ai-immersive-visionos
cyberagentdevelopers
PRO
1
110
Forget efficiency – Become more productive without the stress
ufried
0
120
30万人が利用するチャットをFirebase Realtime DatabaseからActionCableへ移行する方法
ryosk7
5
350
「視座」の上げ方が成人発達理論にわかりやすくまとまってた / think_ perspective_hidden_dimensions
shuzon
2
1.5k
【若手エンジニア応援LT会】AWSで繋がり、共に成長! ~コミュニティ活動と新人教育への挑戦~
kazushi_ohata
0
180
フルカイテン株式会社 採用資料
fullkaiten
0
36k
スプリントゴールにチームの状態も設定する背景とその効果 / Team state in sprint goals why and impact
kakehashi
2
100
VPC間の接続方法を整理してみた #自治体クラウド勉強会
non97
1
830
Featured
See All Featured
10 Git Anti Patterns You Should be Aware of
lemiorhan
654
59k
Imperfection Machines: The Place of Print at Facebook
scottboms
264
13k
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
Scaling GitHub
holman
458
140k
What's in a price? How to price your products and services
michaelherold
243
12k
Unsuck your backbone
ammeep
668
57k
A designer walks into a library…
pauljervisheath
202
24k
BBQ
matthewcrist
85
9.3k
Bash Introduction
62gerente
608
210k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
26
1.4k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
355
29k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
28
9.1k
Transcript
一緒に使うことが多い値は 別クラスにしよう (Data Clumps) リーダブルコード LT会 - vol.4 #readablelt 20220824
きり丸(水上 皓登)@nainaistar
※ Javaのソースコードで例示します。
名前:きり丸(水上 皓登) twitter:nainaistar GitHub:hirotoKirimaru ブログ:きり丸の技術日記 https://nainaistar.hatenablog.com/ 3 一度でいいから バズってみたい
良いコードとは?
良いコードとは悪くないコード
悪くないコードにするには、 Code Smellsに気を付けたらいい
今日のキーワード Data Clumps (データの塊)
Data Clumpsとは (データの塊) 関連性が高く、一緒に使われることが多いデータを 別のクラスにまとめること。 モデリングをしっかりしている人にとっては、 当たり前といえば当たり前のお話です。
期間(日付)
例えば、期間(日付) 開始日 または 終了日を纏めたクラスがあるとします。 最低限の仕様としては次の挙動が考えられます • 存在する日付であること(2022/02/31は許容しない) ◦ 文字列型ではなく、日付型なら発生しない • 開始日は必須であること(終了日がないパターンは期間無限) •
開始日のほうが終了日より古いこと(期間の逆転禁止)
例えば、 期間(日付) public class サブスクリプション { String id; LocalDate contractStartDate;
LocalDate contractEndDate; int qty; BigDecimal price; } サブスクリプション のモデル。 契約開始日、契約終了日 を持っています。
例えば、 期間(日付) public class サブスクリプション { String id; 期間 contractTerm;
int qty; BigDecimal price; } public record 期間( LocalDate start, LocalDate end ){ } ちょっとすっきり。 契約の開始日ではなく、 開始日、終了日と 単語を短縮できるのも好き。
例えば、期間(日付) 複雑な仕様も閉じ込めることができます • 経過期間の計算 ◦ 2022/01/15-2022/12/31 -> 11ヵ月?12ヵ月? • 期間が暦上の一か月で割り切れるかどうか。
◦ 開始日に対して、日割の発生しない終了日であるか • 期間の重複チェック ◦ A.start <= B.end && B.start <= A.end
例えば、期間(日付) 期間と期間の比較は初見は面倒。
期間(時刻)
例えば、期間(時刻) 株式市場の取引時間をアプリで管理するとします。 日本市場:0900-1500(9時から15時) 米国市場:2330-0600(23時30分から翌6時) 仮想通貨:0000-2400(24時間いつでも可能)
例えば、 期間(時刻) public class 取引時間 { String id; String startTime;
String endTime; public isBusinessTime(LocalDateTime now) { // いい感じの処理 } } Stringの”0900”と”1500” を元に、 いい感じに処理する
例えば、期間(時刻) 日付を跨ぐ処理はどうしよう…? 2400が上限値ではない? 休み時間は…? Stringをたくさんいじりたくないな…
例えば、期間(時刻) 開始時刻は同じように管理して、 終了時刻ではなくN時間連続して 営業していることを示せばいいのでは? フレックスのように、 9:00勤務開始、18:00勤務終了 が大事ではなく、勤務時間が8H確保していることが大事では?
例えば、 期間(時刻) public class 取引時間 { String id; String startTime;
int straightHours; public isBusinessTime(LocalDateTime now) { // いい感じの処理 } } 終了時刻はすぐにわからない が、日付を跨ぐ場合には優し いかもしれない
例えば、期間(時刻) このモデルが絶対的な正義だという話ではありません。 必要なデータだけに絞って注目し、 制約事項を丁寧に処理していた結果、 当初、想像できなかったモデリングに 導かれることがありうるという例です。
DBの複合キー
None
DBの複合キー public class 企業 { String 企業ID; } public class
契約 { String 企業ID; String 契約ID; } public class 販売 { String 企業ID; String 契約ID; String 販売ID; } public class 仕入 { String 企業ID; String 契約ID; String 販売ID; String 仕入ID; } 適切な例が思い浮かばず…
例えば、DBの複合キー 複合キー自体が、既にデータの塊。 あくまで一意に定めるための塊であり、振る舞いを持つものではない ただ、名前を持ったデータの塊は意図が非常に伝わりやすい。 例: ・企業IDと契約IDと販売IDから仕入全体の金額を知りたい ・この販売の仕入全体の金額を知りたい
※ 私がコードリーディングする時の 脳内に浮かんでいる図です
DBの複合キー public class 販売主キー { String 企業ID; String 契約ID; String
販売ID; } public class 仕入 { String 企業ID; String 契約ID; String 販売ID; String 仕入ID; # パラメータが一つにまとまる public boolean salesEquals(販売主キー id) { return (this.企業ID.equals(id.企業ID) && this.契約ID.equals(id.契約ID) && this.販売ID.equals(id.販売ID)) } } 販売の複合主キーとして、親 のIDをすべて持つ前提
DBの複合キー 企業IDと 契約IDと 販売IDから 仕入全体の金額を知りたい あまりER図は意識しない
仕入 企業IDで絞込 契約IDで絞込 販売IDで絞込
DBの複合キー この販売の 仕入全体の金額を知りたい コードリーディングで 認識すべき内容が減る
仕入 仕入 販売 販売
例えば、DBの複合キー ばらばらのデータだと、それぞれの値に関係性がない可能性がある。 一つのデータの塊にすると、意図を伝えてくれる。 特にER図で考えたときに、複合主キーという存在は、 データの絞込ではなく、一意に決定づけられるため、 思考コストが減る。
ちょっと脱線
ちょっと脱線 • オブジェクト化したからと言って、 DBの構造も合わせる必要はありません。
例えば、 期間(日付) public class 販売 { String id; 期間 salesTerm;
int qty; BigDecimal price; } public record 期間( LocalDate start, LocalDate end ){ } Javaの構造。
例えば、 期間(日付) CREATE TABLE 販売 ( id VARCHAR(13) PRIMARY KEY,
start_date TIMESTAMP, end_date TIMESTAMP, qty INTEGER, price INTEGER, ); DBの構造。
例えば、 期間(日付) CREATE TABLE 販売 ( id VARCHAR(13) PRIMARY KEY,
term_id VARCHAR(13), qty INTEGER, price INTEGER, ); CREATE TABLE 期間 ( id VARCHAR(13) PRIMARY KEY, start_date TIMESTAMP, end_date TIMESTAMP, ) わざわざ ここまでする必要はない。
例えば、 期間(時刻) CREATE TABLE 取引時間 ( id VARCHAR(13) PRIMARY KEY,
start_time VARCHAR(4), end_time VARCHAR(4), ); CREATE TABLE 取引時間 ( id VARCHAR(13) PRIMARY KEY, start_time VARCHAR(4), straight_hours INTEGER ); モデリングの結果、 DBの構造を変えること自体は 良いこと
ちょっと脱線 • 使いづらくなったら、 データ構造を解体してフラットにするのは全然あり
例えば、 期間(日付) public class サブスクリプション { 期間 contractTerm; LocalDate cancelDate;
public boolean isCancelable{ // 期間クラスの開始日を使用する。 // 別クラスの属性を欲しがるという意味のCode Smells // Feature Envy // が起きかねない return this.contractTerm.start .isAfter(cancelDate) } } 解約日 という概念を追加。
例えば、 期間(日付) public class サブスクリプション { 期間 contractTerm; LocalDate cancelDate;
} ↓ public class サブスクリプション { LocalDate contractStartDate; LocalDate contractEndDate; LocalDate cancelDate; public boolean cancelable?(){ return // 期間クラスに // 解約可能かどうか判断メソッドを作ってもいい // 作らなくてもいい new 期間(contractStartDate, cancelDate) .noProration() } } 使いづらかったら、 データ構造をフラットに するのはあり。
ちょっと待って メリットだけなの?
メリットだけなの? • プリミティブな値をオブジェクト化しているので、 意図せずに別インスタンスから同一インスタンスを参照してしまう可能性 があります。 また、コピーも面倒です。 JavaScriptでは {...object} のシャローコピーで済むのに、 ディープコピーをするためにLodashを使う必要があります。
メリットだけなの? • どこまで処理を閉じ込めるかは議論の余地があります。 サブスクリプションを解約する際に、解約時点で利用が不可能になるか、 月末まで利用可能か、日割りが絡む中途半端な時期に解約できないか、 違約金を払えば解約できるのか…。 営業時間にサマータイムの時間は入りますか?
メリットだけなの? • 無難な処理については間違いなく含めていいです。 ただ、業務色が強い機能については、検討の余地があります。 もちろん、「期間」という一般名詞ではなく、 「立合時間」(株式が取引可能な時間)といった業務に特化した名前があ るのであれば、含めても問題ないでしょう。 🤼< 適切かどうかチームで話し合おう
その他の例
他にも • 期間 ◦ 開始日 と 終了日 ◦ 開始時間 と
継続時間 • 行列 ◦ X と Y と Z ▪ 加算・減算・内積・外積 等々
例えば • ページング ◦ 現在ページ数 と 最大ページ数 と ページサイズ ◦
ソート ▪ キー と 優先度 と 順番(ASC, DESC) と Nullableの扱い • LIMIT, OFFSETをまとめたRowBounds
まとめ
まとめ 従えば必ず有力になる強力なルールではありません。 導入せずとも、特に困ることは無いでしょう。 ただ、データをまとめていくことで、 より洗礼されたモデルになる可能性があります。 期間という概念はどのシステムでも実装する概念だと思いますので、 まずは期間からまとめてみてはいかがでしょうか。
Appendix
例えば、期間(日付) 個人的には、日付の比較が非常に苦手なので、閉じ込めてしまいたいです。 これが嫌い。 AがBより古い(同値含まず): A.isBefore(B) AがBより古い(同値含む) :!A.isAfter(B)
話すこと / 話さないこと • Code Smells • Primitive Obsession •
Java • Primitive Obsession が有効ではない個所 (私もわからない) 詰めなおしは推奨されないので、 べき論が分からない 話すこと 話さないこと
対象者 / 非対象者 • リファクタリングをしたいけど、 ヒントが分からない人 • リファクタリングに興味が無い人 • 動的型付言語
対象者 非対象者
登壇を見た人への期待するアクション • Code Smellsに興味を持つ • 型を作ることをやってみる アクション