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
リーダブルコード by DDD / Readable Code by DDD
Search
Yoshiki Iida
July 07, 2021
Programming
21
12k
リーダブルコード by DDD / Readable Code by DDD
リーダブルコード by DDD
モデリングを起点に可読性の高いコードを実現する
Yoshiki Iida
July 07, 2021
Tweet
Share
More Decks by Yoshiki Iida
See All by Yoshiki Iida
自律的なスケーリング手法FASTにおけるVPoEとしてのアカウンタビリティ / dev-productivity-con-2025
yoshikiiida
2
22k
エンジニアリングマネージャー視点での、自律的なスケーリングを実現するFASTという選択肢 / RSGT2025
yoshikiiida
5
9.2k
ログラスが面白いと思う理由をマネージャーがエモく語ってみる / 20240829 vs LT
yoshikiiida
1
920
質とスピードを両立するログラスのホールチームQA / 20240827 QASaaS_findy
yoshikiiida
2
780
エンジニア組織30人の壁を超えるための 評価システムとマネジメントのスケール / Scaling evaluation system and management
yoshikiiida
12
3.8k
スクラムの成熟と壁 〜スケーリングの議論から見えたもの〜 / Maturity and barriers in Scrum
yoshikiiida
4
2k
スタートアップにおける組織設計とスクラムの長期戦略 / Scrum Fest Kanazawa 2024
yoshikiiida
17
6.5k
ログラスの選考プロセスにおけるアトラクト戦略 / Attraction strategy in Loglass interview process
yoshikiiida
7
3.8k
QA経験のないエンジニアリング マネージャーがQAのカジュアル面談に出て 苦労していること・気づいたこと / scrum fest niigata 2024
yoshikiiida
2
5.7k
Other Decks in Programming
See All in Programming
[Codecon - 2025] Como não odiar seus testes
camilacampos
0
100
Gemini CLI のはじめ方
ttnyt8701
1
110
코딩 에이전트 체크리스트: Claude Code ver.
nacyot
0
1k
AI時代の『改訂新版 良いコード/悪いコードで学ぶ設計入門』 / ai-good-code-bad-code
minodriven
24
10k
バイブコーディング超えてバイブデプロイ〜CloudflareMCPで実現する、未来のアプリケーションデリバリー〜
azukiazusa1
2
730
抽象化という思考のツール - 理解と活用 - / Abstraction-as-a-Tool-for-Thinking
shin1x1
1
860
QA x AIエコシステム段階構築作戦
osu
0
210
ソフトウェア設計とAI技術の活用
masuda220
PRO
25
6.9k
商品比較サービス「マイベスト」における パーソナライズレコメンドの第一歩
ucchiii43
0
220
マッチングアプリにおけるフリックUIで苦労したこと
yuheiito
0
240
Strands Agents で実現する名刺解析アーキテクチャ
omiya0555
1
110
AIに安心して任せるためにTypeScriptで一意な型を作ろう
arfes0e2b3c
0
270
Featured
See All Featured
Gamification - CAS2011
davidbonilla
81
5.4k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
How to train your dragon (web standard)
notwaldorf
96
6.1k
Navigating Team Friction
lara
187
15k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Building Better People: How to give real-time feedback that sticks.
wjessup
367
19k
The World Runs on Bad Software
bkeepers
PRO
70
11k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3.1k
It's Worth the Effort
3n
185
28k
Writing Fast Ruby
sferik
628
62k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
1k
Transcript
モデリングを起点に可読性の高いコードを実現する 2021/07/07 #readablelt Yoshiki Iida リーダブルコード by DDD
Yoshiki Iida (@ysk_118) エンジニアに始まり、スクラムマスター、プロダクトオーナー、マネージャー、執行 役員を経験し、現場のチームビルディングから部署を超えた会社全体の改善な ど、アジャイルな組織づくりの推進を行ってきました。現在は株式会社ログラスに てソフトウェアエンジニアとしてプロダクト開発に携わっています。 書籍「Scrum Boot Camp
The Book 増補改訂版」コラムニスト。 一般社団法人アジャイルチームを支える会 理事。 $ whoami
ログラスについて は、事業進捗を可視化することで 柔軟で高精度な経営推進を実現する プランニング・クラウドサービスです。
ログラスについて
ログラスについて
• コードがリーダブルであることとDDDの関連 • 実践プラクティス Topic
コードがリーダブルであるとは?
コードがリーダブルであるとは? 他の人が最短時間で理解できること
理解できるとは?
理解できるとは? • 例えば、あるクラスが何をしているか理解できるとは? ◦ 何をしているのかわかる ▪ 状態/振る舞い ◦ 依存関係がわかる ▪
どこから呼ばれているか ▪ 何を呼んでいるか ◦ どんなビジネス的な意味を持っているかわかる
理解できるとは? • 例えば、あるクラスが何をしているか理解できるとは? ◦ 何をしているのかわかる ▪ 状態/振る舞い ◦ 依存関係がわかる ▪
どこから呼ばれているか ▪ 何を呼んでいるか ◦ どんなビジネス的な意味を持っているかわかる ここまでは時間をかけて コードを読み込めば なんとかなるかも これはコードだけでは わからないかもしれない
理解できないとどうなるか? • 実装の手戻り • 実装の歪み ◦ その後の変更可能性を著しく下げる • 品質の低下 ◦
不十分なテスト観点 • 低品質の再生産 ◦ コピペによる増殖
システムを正しく作るためにはどちらも重要 実装の理解しやすさ 実装の目的の理解しやすさ
システムを正しく作るためにはどちらも重要 実装の理解しやすさ 実装の目的の理解しやすさ → DDDというアプローチ
ドメインモデリングから始まる実装 実装の理解しやすさ 実装の目的の理解しやすさ ドメインモデリング 実装 & リファクタリング
ドメインモデリングの位置付け ビジネス的な一次情報 • ユーザーヒアリング • ユースケース ドメインモデリング • ドメインモデル図 仕様・テストケース
• 仕様 • テストケース • 受入基準 ↑ ビジネスとシステムをつなぐ 最も重要なプロセス
ドメインモデリングのサンプル /** * コメント */ class Comment private constructor( val
id: ID<Comment>, val commentText: String, val isResolved: Boolean, val createdAt: DateTime, val updatedAt: DateTime, val createdUserId: ID<User>, val commentType: CommentType, /** コメントの条件 */ val condition: CommentCondition, ) { companion object { fun create( commentText: String, createdUserId: ID<User>, commentType: CommentType, condition: CommentCondition, ): Comment { validationCommentTextLength(commentText) val createdAt = DateTime.now() return Comment( id = ID.gen(), commentText = commentText, isResolved = false, createdAt = createdAt, updatedAt = createdAt, createdUserId = createdUserId, condition = condition, commentType = commentType, ) } } } /** * コメントの条件 */ data class CommentCondition( val departmentId: ID<Department>, val projectId: ID<Project>, val yearMonth: YearMonth, ) ドメインモデル を元に実装する データの実例を併記しておくとわかりやすい
実装の目的の理解しやすさ • ドメインモデル図があれば登場する概念を一目で把握できる • ユースケースがあればそれらの概念がどう使われるのか理解できる • そこから仕様を導き出すことができ、 その仕様を検証するためのテストケースを作成することができる ここを最初に押さえていれば 後からjoinする人も実装の目的は理解しやすい
余談: アンチパターン • 実装の目的が失われてしまったシステムは変更難易度が桁違いになる • 「なぜこの概念が必要なのか・・?」「この依存は必要なのか・・?」「この挙 動でユースケースを満たしているのか・・?」 ◦ コードを考古学して答えが見つかれば良いが最終的にエスパーで妥 協しなければいけないケースもありうる
実装の理解しやすさ • 重要な要素 ◦ 責務 ◦ テスト ◦ リファクタリング
責務 責務とは、そのクラスが行うこと/そのクラスが表すもの • 責務を1つに絞っていくことでリーダブルになること ◦ クラスの関心ごとが小さくなり理解しやすくなる ◦ テスト対象が明確になり、テストコード実装しやすくなる ◦ テストがあるとリファクタリングしやすくなり、コードがさらに読みやすく
なる
責務 例えば、ユースケース層では1クラス1パブリックメソッドにしていくと見通しが良くなる class ProjectService( private val tenantSvc: TenantService, private val
repo: ProjectRepository, ) { fun list( tenantId: ID<Tenant>, projectId: ID<Project> ): List<Project> { val projectList = ~~~ return projectList } fun fix( tenantId: ID<Tenant>, projectId: ID<Project> ) { repo.fix(tenantId, projectId) } fun create( tenantId: ID<Tenant>, projectParam: ProjectParam, ): Project { return repo.create(tenantId, projectParam) } } class ListProjectUseCase( private val tenantSvc: TenantService, private val repo: ProjectRepository, ) { fun execute( tenantId: ID<Tenant>, projectId: ID<Project> ): List<Project> { val projectList = ~~~ return projectList } } class CreateProjectUseCase( private val tenantSvc: TenantService, private val repo: ProjectRepository, ) { fun execute( tenantId: ID<Tenant>, projectParam: ProjectParam, ): Project { return repo.create(tenantId, projectParam) } } class FixProjectUseCase( private val tenantSvc: TenantService, private val repo: ProjectRepository, ) { fun execute( tenantId: ID<Tenant>, projectId: ID<Project> ) { repo.fix(tenantId, projectId) } }
テスト リファクタリングしやすくなる。変更しやすくなる。テストは資産! • テストに関するコメント • テストデータの可視化 • 日本語変数を活用する
テストに関するコメント • 本質的なテストコード以外を小さくし、何をテストしているのかわかりやすく する @Test fun `インサートしたデータが削除できること`() { // given:
val comment = createComment() dataCreators.comment.create(tenant.tenantId, listOf(comment)) // 事前確認: 件数が一件取得される commentRepository.findById(tenant.tenantId, comment.id) .also { assertNotNull(it) } // when: ID指定して削除すると commentRepository.deleteById(tenant.tenantId, comment.id) // then: ID指定で取得できなくなる commentRepository.findById(tenant.tenantId, comment.id) .also { assertNull(it) } } データ準備はprivate method化 when, thenで何をした結果 どうなるかを明確化
テストデータの可視化 • 大きなオブジェクトのテストデータはコメントなどで表現するのは限界がある ◦ 割り切ってスプレッドシートリンクをテストコードに残す internal class ComparisonFlatTreeV3ViewTest { /**
* テストデータの可視化 : https://docs.google.com/spreadsheets/d/1Gd_xxx---xx_XX/edit#gid=xx */ @Test fun`対比表Viewの数値部分 _月毎`() {
日本語変数の活用 • 英語にすると馴染みがなくて可読性が低くなるような場合は日本語変数も アリ ◦ テストコードなどでは有用 val 当期純利益 = result.treeView.items.find
{ it.id == "XXXXXXXXXXXXX" }!! 当期純利益.also { it -> val jan = it.data.find { it.periodKey == "2020-01" }!! assertEquals(expected = BigDecimal(10000), actual = jan.actual.sum) val feb = it.data.find { it.periodKey == "2020-02" }!! assertEquals(expected = BigDecimal(20000), actual = feb.actual.sum) val mar = it.data.find { it.periodKey == "2020-03" }!! assertEquals(expected = BigDecimal(30000), actual = mar.actual.sum) }
リファクタリング 絶え間なく行う • 個人的によくやるもの ◦ 責務を細かく分割する ◦ プリミティブ型やタプルに対して名前をつけた型を用意する ◦ SLAPでメソッドの粒度を揃える
◦ ...
その他 • 見た目の美しさ&統一はIDEとlinterに任せる • 命名はガイドライン化し認知コスト・思考コストを下げる • 参照実装のガイドライン化 ◦ 真似して欲しいものとしてまとめておく •
コメント ◦ 真似して欲しくないものをしっかり書く
命名のガイドライン化 命名がブレやすいものは認知コストにな るためガイドライン化。 変更したければPullRequestベースで 更新していく。
真似して欲しいもの オンボーディング資料に参照実装として 積極的に真似して欲しい実装をまとめて ある
真似して欲しくないもの // TODO: TestXxxFactory実装に寄せる private fun mockDepartment(id: String) = Department.of(
id = ID.from(id), tenantId = tenantId, code = Code.of(id), name = Name.of(id), externalSystemDepartmentCode = null ) 新たに汎用的な仕組みを作ったので 古い書き方がコピペされないように 機械的にTODOを埋め込む override fun insert(tenantId: ID<Tenant>, account: Account) { // TODO: バリデーションはドメイン層に委譲 if (account.accountType.isAggregationType()) throw BadRequestException("科目種別はSTANDARD_PL_AGGRには設定できません ") insert(listOf(account)) } このレイヤの責務 でないものを明示
• 変更しやすいシステムの特徴 ◦ リーダブルであること ▪ 実装を理解しやすいこと ▪ 実装の目的を理解しやすいこと • 実践アプローチ
◦ ドメインモデリングは実装の目的理解にダイレクトに有効 ◦ 責務を意識し、テスト・リファクタを回していくことで 結果的にリーダブルになっていく ◦ 参照実装などを充実させることでチーム開発がより効率化 まとめ