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
26k
エンジニアリングマネージャー視点での、自律的なスケーリングを実現するFASTという選択肢 / RSGT2025
yoshikiiida
5
10k
ログラスが面白いと思う理由をマネージャーがエモく語ってみる / 20240829 vs LT
yoshikiiida
1
950
質とスピードを両立するログラスのホールチームQA / 20240827 QASaaS_findy
yoshikiiida
2
880
エンジニア組織30人の壁を超えるための 評価システムとマネジメントのスケール / Scaling evaluation system and management
yoshikiiida
12
3.9k
スクラムの成熟と壁 〜スケーリングの議論から見えたもの〜 / Maturity and barriers in Scrum
yoshikiiida
4
2k
スタートアップにおける組織設計とスクラムの長期戦略 / Scrum Fest Kanazawa 2024
yoshikiiida
17
6.6k
ログラスの選考プロセスにおけるアトラクト戦略 / Attraction strategy in Loglass interview process
yoshikiiida
7
3.9k
QA経験のないエンジニアリング マネージャーがQAのカジュアル面談に出て 苦労していること・気づいたこと / scrum fest niigata 2024
yoshikiiida
2
5.9k
Other Decks in Programming
See All in Programming
Le côté obscur des IA génératives
pascallemerrer
0
120
CSC305 Lecture 01
javiergs
PRO
1
400
Let's Write a Train Tracking Algorithm
twocentstudios
0
220
uniqueパッケージの内部実装を支えるweak pointerの話
magavel
0
920
株式会社 Sun terras カンパニーデック
sunterras
0
220
プロダクト開発をAI 1stに変革する〜SaaS is dead時代で生き残るために〜 / AI 1st Product Development
kobakei
0
490
2分台で1500examples完走!爆速CIを支える環境構築術 - Kaigi on Rails 2025
falcon8823
3
3k
Advance Your Career with Open Source
ivargrimstad
0
330
Local Peer-to-Peer APIはどのように使われていくのか?
hal_spidernight
2
450
CSC305 Lecture 04
javiergs
PRO
0
250
CSC509 Lecture 01
javiergs
PRO
1
430
ポスターセッション: 「まっすぐ行って、右!」って言ってラズパイカーを動かしたい 〜生成AI × Raspberry Pi Pico × Gradioの試作メモ〜
komofr
0
940
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
40
2.1k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.1k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
19
1.2k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Code Review Best Practice
trishagee
72
19k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
30
2.9k
jQuery: Nuts, Bolts and Bling
dougneiner
64
7.9k
Writing Fast Ruby
sferik
629
62k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
890
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
54
3k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
610
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)) } このレイヤの責務 でないものを明示
• 変更しやすいシステムの特徴 ◦ リーダブルであること ▪ 実装を理解しやすいこと ▪ 実装の目的を理解しやすいこと • 実践アプローチ
◦ ドメインモデリングは実装の目的理解にダイレクトに有効 ◦ 責務を意識し、テスト・リファクタを回していくことで 結果的にリーダブルになっていく ◦ 参照実装などを充実させることでチーム開発がより効率化 まとめ