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
ログラスが面白いと思う理由をマネージャーがエモく語ってみる / 20240829 vs LT
yoshikiiida
1
740
質とスピードを両立するログラスのホールチームQA / 20240827 QASaaS_findy
yoshikiiida
2
130
エンジニア組織30人の壁を超えるための 評価システムとマネジメントのスケール / Scaling evaluation system and management
yoshikiiida
10
3.3k
スクラムの成熟と壁 〜スケーリングの議論から見えたもの〜 / Maturity and barriers in Scrum
yoshikiiida
4
1.8k
スタートアップにおける組織設計とスクラムの長期戦略 / Scrum Fest Kanazawa 2024
yoshikiiida
17
5.9k
ログラスの選考プロセスにおけるアトラクト戦略 / Attraction strategy in Loglass interview process
yoshikiiida
7
2.9k
QA経験のないエンジニアリング マネージャーがQAのカジュアル面談に出て 苦労していること・気づいたこと / scrum fest niigata 2024
yoshikiiida
2
3.5k
ログラスにおけるコード品質でビジネスに貢献する仕組み・カルチャー / A system and culture that contributes to business through code quality in Loglass
yoshikiiida
12
2.2k
エンジニア採用責任者と人事の邂逅 / Engineer hiring manager meet HR
yoshikiiida
2
590
Other Decks in Programming
See All in Programming
Amazon Qを使ってIaCを触ろう!
maruto
0
400
アジャイルを支えるテストアーキテクチャ設計/Test Architecting for Agile
goyoki
9
3.3k
よくできたテンプレート言語として TypeScript + JSX を利用する試み / Using TypeScript + JSX outside of Web Frontend #TSKaigiKansai
izumin5210
6
1.7k
シールドクラスをはじめよう / Getting Started with Sealed Classes
mackey0225
4
640
初めてDefinitelyTypedにPRを出した話
syumai
0
410
as(型アサーション)を書く前にできること
marokanatani
10
2.6k
どうして僕の作ったクラスが手続き型と言われなきゃいけないんですか
akikogoto
1
120
AWS Lambdaから始まった Serverlessの「熱」とキャリアパス / It started with AWS Lambda Serverless “fever” and career path
seike460
PRO
1
260
Laravel や Symfony で手っ取り早く OpenAPI のドキュメントを作成する
azuki
2
120
Webの技術スタックで マルチプラットフォームアプリ開発を可能にするElixirDesktopの紹介
thehaigo
2
1k
レガシーシステムにどう立ち向かうか 複雑さと理想と現実/vs-legacy
suzukihoge
14
2.2k
Better Code Design in PHP
afilina
PRO
0
120
Featured
See All Featured
VelocityConf: Rendering Performance Case Studies
addyosmani
325
24k
Embracing the Ebb and Flow
colly
84
4.5k
Code Review Best Practice
trishagee
64
17k
10 Git Anti Patterns You Should be Aware of
lemiorhan
654
59k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Building Your Own Lightsaber
phodgson
103
6.1k
Intergalactic Javascript Robots from Outer Space
tanoku
269
27k
Optimising Largest Contentful Paint
csswizardry
33
2.9k
Into the Great Unknown - MozCon
thekraken
32
1.5k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
16
2.1k
BBQ
matthewcrist
85
9.3k
Designing for Performance
lara
604
68k
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)) } このレイヤの責務 でないものを明示
• 変更しやすいシステムの特徴 ◦ リーダブルであること ▪ 実装を理解しやすいこと ▪ 実装の目的を理解しやすいこと • 実践アプローチ
◦ ドメインモデリングは実装の目的理解にダイレクトに有効 ◦ 責務を意識し、テスト・リファクタを回していくことで 結果的にリーダブルになっていく ◦ 参照実装などを充実させることでチーム開発がより効率化 まとめ