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という選択肢 / RSGT2025
yoshikiiida
5
5.1k
ログラスが面白いと思う理由をマネージャーがエモく語ってみる / 20240829 vs LT
yoshikiiida
1
830
質とスピードを両立するログラスのホールチームQA / 20240827 QASaaS_findy
yoshikiiida
2
210
エンジニア組織30人の壁を超えるための 評価システムとマネジメントのスケール / Scaling evaluation system and management
yoshikiiida
11
3.5k
スクラムの成熟と壁 〜スケーリングの議論から見えたもの〜 / Maturity and barriers in Scrum
yoshikiiida
4
1.9k
スタートアップにおける組織設計とスクラムの長期戦略 / Scrum Fest Kanazawa 2024
yoshikiiida
17
6.1k
ログラスの選考プロセスにおけるアトラクト戦略 / Attraction strategy in Loglass interview process
yoshikiiida
7
3.2k
QA経験のないエンジニアリング マネージャーがQAのカジュアル面談に出て 苦労していること・気づいたこと / scrum fest niigata 2024
yoshikiiida
2
4.4k
ログラスにおけるコード品質でビジネスに貢献する仕組み・カルチャー / A system and culture that contributes to business through code quality in Loglass
yoshikiiida
12
2.4k
Other Decks in Programming
See All in Programming
苦しいTiDBへの移行を乗り越えて快適な運用を目指す
leveragestech
0
930
パスキーのすべて ── 導入・UX設計・実装の紹介 / 20250213 パスキー開発者の集い
kuralab
3
870
クリーンアーキテクチャから見る依存の向きの大切さ
shimabox
5
930
Bedrock Agentsレスポンス解析によるAgentのOps
licux
3
900
メンテが命: PHPフレームワークのコンテナ化とアップグレード戦略
shunta27
0
290
ソフトウェアエンジニアの成長
masuda220
PRO
12
2.1k
コードを読んで理解するko build
bells17
1
100
PHPのバージョンアップ時にも役立ったAST
matsuo_atsushi
0
210
Multi Step Form, Decentralized Autonomous Organization
pumpkiinbell
1
830
2025.2.14_Developers Summit 2025_登壇資料
0101unite
0
150
Jakarta EE meets AI
ivargrimstad
0
260
『GO』アプリ データ基盤のログ収集システムコスト削減
mot_techtalk
0
140
Featured
See All Featured
Building Applications with DynamoDB
mza
93
6.2k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
27
1.9k
Visualization
eitanlees
146
15k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
33
2.8k
GraphQLの誤解/rethinking-graphql
sonatard
68
10k
Designing for Performance
lara
604
68k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
49
2.3k
A Philosophy of Restraint
colly
203
16k
Done Done
chrislema
182
16k
How to Ace a Technical Interview
jacobian
276
23k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
4
430
How to Think Like a Performance Engineer
csswizardry
22
1.4k
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)) } このレイヤの責務 でないものを明示
• 変更しやすいシステムの特徴 ◦ リーダブルであること ▪ 実装を理解しやすいこと ▪ 実装の目的を理解しやすいこと • 実践アプローチ
◦ ドメインモデリングは実装の目的理解にダイレクトに有効 ◦ 責務を意識し、テスト・リファクタを回していくことで 結果的にリーダブルになっていく ◦ 参照実装などを充実させることでチーム開発がより効率化 まとめ