SlideShare a Scribd company logo
1 of 27
Download to read offline
キレイなコードの書き⽅
2016年11⽉17⽇
NCデザイン&コンサルティング株式会社
北村 拓也 - @chipstar_light
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
今回のコンテキスト
n  プログラマ
n  Java
•  オブジェクト指向
•  静的型付け⾔語(コンパイル⾔語)
n  業務システム
•  ⻑期間の保守・エンハンス
2
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
キレイなコードとは何か?
n  誰でも理解コード?
n  変更しやすいコード?
n  重複が少ないコード?
n  無駄な処理が含まれていないコード?
n  ちゃんとコメントが書かれているコード?
n  会社の規約に沿ったコード?
n  真の意味でオブジェクト指向を実践したコード?
n  etc…
3
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
コード品質の指標
4
指標 説明
可読性(解析性) 他者がソースコードを⾒て処理の内容を理解できる、または処理の内容を解析できること。
保守性
(修正容易性)
予想できなかった変更を、最低限の修正で実現できること。
重複がなく変更箇所を局所化し、他のコンポーネントへの影響を最⼩にすること。
例:不具合の修正やミドルウェアの変更の場合に、修正箇所が少ない
拡張性 事前に想定できる機能の追加や拡張を、既存コードに影響を与えず実現できること。
拡張が想定されるコンポーネントを、既存コードを修正する事無く、差し込みや差し替え可能
な構造になっていること。
例:認証⽅式をDBからLDAPに切り替える際に、LDAP版の認証モジュールをプラグインできる。
再利⽤性 コードの重複がなく、複数存在する同⼀ロジックの処理を1つのモジュールで実現しているこ
と。
コードが再利⽤可能な粒度で汎⽤的なモジュールに分割されていること。
運⽤性 障害発⽣時にトラブルシューティングしやすい状態にあること。
環境依存のパラメータをプログラムを変更する事無く切り替えられる仕組みとなっていること。
その他の指標 上述以外の品質指標。
それぞれの品質指標を⾼い⽔準で満たしている事。
n  よく使われるコードの品質を測るための指標
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
キレイなコードを書くためには…
n  キレイなコードであると判断する基準はいくつもある
n  個々の基準を満たすためにそれぞれのアプローチがある
n  キレイなコードの定義を⼀⾔で⾔い表す事は難しく、実践
⽅法も⼀筋縄でいかない
n  この状況をわかりやすく表すために、キレイなコードを書
くためには「センス」が必要だと表現される
n  このキレイさの「センス」を⾝につけるには、無数の⼩さ
な⼿法を知識として蓄え、⼀つ⼀つ丁寧に実践していく必
要がある
5
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
⼀般的に⾏われているアプローチを学ぶ
n  今⽇は以下の書籍の中で語られている「キレイなコードを
書く」ための代表的なアプローチを学ぶ
•  時間が限られているので、あくまでも⼀部のみ
6
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
オブジェクト指向エクササイズ
n  オブジェクト指向の良さを⽣み出す9つのルール
1.  1つのメソッドにつきインデントは1段階までにすること
2.  else句を使⽤しないこと
3.  すべてのプリミティブ型と⽂字列型をラップすること
4.  1⾏につきドットは1つまでにすること
5.  名前を省略しないこと
6.  すべてのエンティティを⼩さくすること
7.  1つのクラスにつきインスタンス変数は2つまでにすること
8.  ファーストクラスコレクションを使⽤すること
9.  Getter/Setter/プロパティを使⽤しないこと
7
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
基礎編
8
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
意味のある名前をつける
n  変数、関数、引数、クラス、パッケージの全てにおいて、注意深く、意図が
明確な名前を付ける
n  偽や曖昧な名前を避け、コード中での意味が変わったら、速やかに意味に合
わせた名前に変える
n  エンコーディングが必要な名前にしない
n  クラス名には名詞を、メソッド名には動詞を使う
n  システム内で同じ抽象概念、コンセプトには同じ命名規則を適⽤して⼀貫性
を持たせる
n  ビジネスドメインの⽤語を使⽤する
n  多少⻑くなっても意図を理解できる名前を付ける事を優先する           
9
public	List<int[]>	getThem()	{	
				List<int[]>	list1	=	new	ArrayList<>();	
				for	(int[]	x	:	theList)	
								if	(x[0]	==	4)	
												list.add(x);	
				return	list1;	
}	
public	List<int[]>	getFlaggedCells()	{	
				List<int[]>	flaggedCells	=	new	ArrayList<>();	
				for	(int[]	cell	:	gameBoard)	
								if	(cell[STATUS_VALUE]	==	FLAGGED)	
												flaggedCells.add(cell);	
				return	flaggedCells;	
}
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
関数を⼩さく保つ
n  ⼀にも⼆にも関数は⼩さく保つこと
n  1つの関数は1つの事だけをきちんと⾏い、それ以外の事
を⾏ってはならない
n  1つの関数は1つの抽象レベルにする
•  抽象度の異なる処理が1つの関数内に混在しないようにする
n  DRY(Don't Repeat Yourself)原則を守る
•  重複しているコードは、それだけで1つの役割を持っている
10
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 11
public	static	String	renderPageWithSetupsAndTeardowns(	
		PageData	pageData,	boolean	includeSuiteSetup)	throws	Exception	{	
		WikiPage	wikiPage	=	pageData.getWikiPage();	
		StringBuilder	buffer	=	new	StringBuilder();	
		if	(pageData.hasAttribute("Test"))	{	
				if	(includeSuiteSetup)	{	
						WikiPage	suiteSetup	=		
								PageCrawlerImpl.getInheritedPage(SUITE_SETUP_NAME,	wikiPage);	
						if	(suiteSetup	!=	null)	{	
								WikiPagePath	pagePath	=	suiteSetup.getPageCrawler().getFullPath(suteSetup);	
								String	pagePathName	=	PathParser.render(pagePath);	
								buffer.append("!include	-setup	.")	
										.append("pagePathName")	
										.append("n");	
						}	
				}	
				WikiPage	setup	=	PageCrawlerImpl.getInheritedPage("SetUp",	wikiPage);	
				if	(setup	!=	null)	{	
						WikiPagePath	setupPath	=	wikiPage.getPageCrawler().getFullPath(setup);	
						String	setupPathName	=	PathParser.render(setupPath);	
						buffer.append("!include	-setup	.")	
								.append(setupPathName)	
								.append("n");	
				}	
		}	
		buffer.append(pageData.getContent());	
		if(pageData.hasAttribute("Test"))	{	
				WikiPage	teardown	=	PageCrawlerImpl.getInheritedPage("TearDown",	wikiPage);	
				if	(teardown	!=	null)	{	
						WikiPagePath	tearDownPath	=		
								suiteSetup.getPageCrawler().getFullPath(teardown);	
						String	tearDownPathName	=	PathParser.render(tearDownPath);	
						buffer.append("n")	
								.append("!include	-teardown	.")	
								.append("tearDownPathName")	
								.append("n");	
				}	
				if	(includeSuiteSetup)	{	
						WikiPage	suiteTeardown	=		
										PageCrawlerImpl.getInheritedPage(SUITE_TEARDOWN_NAME,	wikiPage);	
						if	(suiteTeardown	!=	null)	{	
								WikiPagePath	pagePath	=		
										suiteTeardown.getPageCrawler().getFullPath(suiteTeardown);	
								String	pagePathName	=	PathParser.render(pagePath);	
								buffer.append("!include	-teardown	.")	
										.append("pagePathName")	
										.append("n");	
						}	
				}	
		}	
		pageData.getContent(buffer.toString());	
		return	pageData.getHtml();	
}	
public	static	String	renderPageWithSetupsAndTeardowns(	
		PageData	pagaData,	boolean	isSuite)	throws	Exception	{	
		boolean	isTestPage	=	pageData.hasAttribute("Test");	
		if	(isTestPage)	{	
				WikiPage	testPage	=	pageData.getWikiPage();	
				StringBuilder	newPageContent	=	new	StringBuilder();	
				includeSetupPages(testPage,	newPageContent,	isSuite);	
				newPageContent.append(pageData.getContent());	
				includeTeardownPages(testPage,	newPageContent,	isSuite);	
				pageData.setContent(newPageContent.toString());	
		}	
		return	pageData.getHtml();	
}	
public	static	String	renderPageWithSetupsAndTeardowns(	
		PageData	pagaData,	boolean	isSuite)	throws	Exception	{	
		if	(isTestPage(pageData))	{	
				includeSetupAndTeardownPages(pageData,	isSuite);	
		}	
		return	pageData.getHtml();	
}	
リファクタリング後
さらにリファクタリング後
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
副作⽤を避ける
n  副作⽤とは、関数が1つのことを⾏なうことを保証しつつ、隠れ
て別のことを⾏なうこと
n  誤解を招き、奇妙な時間軸の関連を産み、依存関係の順序を狂わ
せる
n  出⼒引数を避ける
n  コマンド・照会の分離原則を守る
•  関数では、何らかの処理を⾏なうか、何らかの応答を返すかのどちらかを⾏
うべきで、両⽅を⾏ってはならない
12
public	class	UserValidator	{	
		private	Cryptographer	cryptographer;	
		public	boolean	checkPassword(String	userName,	String	password)	{	
				User	user	=	UserGateway.findByName(userName);	
				if	(user	!=	User.NULL)	{	
						String	codedPhrase	=	user.getPhraseEncodedByPassword();	
						String	phrase	=	crytographer.decrypt(codedPhrase,	password);	
						if	("Valid	Password".equals(phrase))	{	
								Session.initialize();	
								return	true;	
						}	
				}	
				return	false;	
		}	
}
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
コメント
n  コメントは必要悪
•  あくまで、コードでうまく表現することに失敗したときに、それを補うのに使う
n  コメントは間違っていても動いてしまう
•  コードは正しく修正されても、コメントは置き去りになりがち
•  コメントはコンパイルされず、テスト出来ないことを意識する
n  コードの意図は、コード⾃⾝で伝える
•  変数名や関数名を利⽤してコードに意図を込める
n  よいコメント
•  公開APIにおけるJavaDoc
•  実装の背景や決定要因を伝えるコメント
13
//	従業員が、給与の完全給付をうけるかどうかをチェック	
if((employee.flags	&	HOURLY_FLAG)	&&	(employee.age	>	65))	
if(emplyee.isEligibleForFullBenefits())	
悪い例 良い例
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
オブジェクト指向編
14
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
凝集度と結合度
n  凝集度
•  クラスやパッケージ内の機能要素と情報要素間の関連性の強さを表す指標
•  互いに関連する機能や情報があちこちに分散していると、仕様変更が⽣じた
場合の影響範囲が広くなってしまう
•  凝集度を⾼めようとすると1つのクラスが持つデータの数がへり、⼩さなク
ラスが⼤量にできるようになる
n  結合度
•  クラスやパッケージ間で、呼び出し関係にあるメソッドの結び付きの強さを
表す指標
•  結合度が⾼くなると複数のクラスやパッケージ間で依存度が上がってしまう
ため、保守やメンテナンス、仕様変更などの対応がしづらくなる
n  凝集度を⾼め、結合度を低くする事で、保守性や拡張性を⾼める
事が必要
15
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
データとロジックの置き場所を同じクラスにする
n  オブジェクトは裏にあるデータを隠して抽象化し、データを操作
する機能を公開するためのもの
•  全てのデータにsetter/getterを⽤意すると本末転倒
n  データ構造の変更が必要になった場合でも、⾃クラス内の修正に
留める事ができる
n  デメテルの法則を守る・守らせる
•  オブジェクトを使⽤する場合、そのオブジェクトの内部についてしるべきで
はない
•  データを隠蔽し、操作を公開する
•  必然的に引数の受け渡しの数が少なくなる
n  データ転送オブジェクトの利⽤を避ける
•  レイヤ設計に伴って仕⽅がない場合にのみ利⽤
16
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
多態性を利⽤する
n  多態性とは、実⾏される処理の実体が、コールされたメッセージ
ではなく、メッセージを受けたオブジェクトによって決定される
性質
•  呼び出し側は、処理を依頼した側の実体を知らなくてよい
n  多態性を活⽤する事により分岐処理を無くす
•  多態性を使って分岐した場合は、新しい分岐候補が追加されても、呼び出し
側の修正が不要
•  保守性や拡張性を上げる事ができる
17
Shape
float	calcTotalArea(List<Shape>	shapes)	{	
				float	totalArea	=	0.0;	
				for	(shape	in	shapes)	{	
								totalArea	+=	shape.getArea();	
				}	
				return	totalArea;	
}	
+ getArea()
Rectangle
+ getArea()
Circle
+ getArea()
Shapeにのみ
依存する
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
SOLID原則を⽤いた設計
n  オブジェクト指向の設計における5つの基本原則
•  単⼀責務の原則(SRP)
•  オープン・クローズドの原則(OCP)
•  リスコフの置換原則(LSP)
•  インターフェース分離の原則(ISP)
•  依存関係逆転の原則(DIP)
18
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
単⼀責務の原則(SRP)
n  「クラスを変更する理由は1つより多く存在してはならない」
n  複数の変更理由が存在するクラスは改修の過程で凝集度が下がっ
てしまい、変更に弱くなる
•  1つの役割の変更が、別の役割の部分にまで影響を及ぼしてしまう
n  責務や役割と捉えると判断が難しいため、「変更の理由」と捉え
て考えてみる
19
Modem
+ dial(String)
+ hangup()
+ send(char)
+ recv()
DataChannel
+ send(char)
+ recv()
Connection
+ dial(String)
+ hangup()
Modem
責務の分割
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
オープン・クローズドの原則(OCP)
n  「ソフトウェアの構成要素は、拡張に対して開いていて、修正に対し
て閉じていなければならない」
n  オープン:拡張に対して開かれている
•  アプリの仕様が変更されても、モジュールに新たな振る舞いを追加するだけでその変
更に対処できる
n  クローズド:修正に対して閉じている
•  モジュールの振る舞いを拡張しても、他のモジュールには影響が出ない、コンパイル
なども不要な状態
n  多態性をうまく活⽤する
20
Client Server Client
<<interface>>
Server
ServerImpl
ClientはServerを参照している
ため、Serverが変更されると
Clientも何らかの変更、または
再コンパイルが必要
Clientはinterfaceを参照してい
るだけなので、ServerImplの
振る舞いが修正されても変更
もコンパイルも不要
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
リスコフの置換原則(LSP)
n  「派⽣型はその基本型と置換可能でなければならない」
•  Tから派⽣したSがあり、Tを参照している任意の処理をSに置き換えても問
題なく動作する事を保証しなければならない
n  例えば、派⽣クラスで親クラスのメソッドをオーバーライドして
振る舞いを壊している、例外を投げているなど
n  または、利⽤クラス側が多態性を無視して具象クラスにキャスト
して利⽤している
n  この原則はコンパイルの仕組だけでは保証できない
n  親と⼦でメソッド毎に事前条件、事後条件を取り決めてそれを守
るようにする
•  そもそも、できるだけ前後の処理に依存しない形でインターフェースを設計
する事
21
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
依存関係逆転の原則(DIP)
n  「上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュール
も"抽象"に依存すべきである。」
n  「"抽象"は実装の詳細に依存してはならない。実装の詳細が"抽象"に依存すべきであ
る。」
n  上位(⽅針を決めるレイヤ)が下位(具体的な実装レイヤ)に依存すると、実装の変更
が⽅針に影響を及ぼしてしまうため
n  多態性をうまく活⽤する
n  レイヤ毎に⾼い凝集性を保つ必要がある
22
Policy Layer
Mechanism Layer
Utility Layer
Policy Layer <<interface>>
Policy Service
Mechanism Layer Mechanism Layer
Utility Layer
Policy
Policy
Policy
悪い例 良い例
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
インターフェース分離の原則(ISP)
n  「クライアントに、クライアントが利⽤しないメソッドへの依存を強
制してはならない」
n  太ったインターフェースを作らないこと
n  様々な⽤途を持った1つのインターフェースを作ると、実装側が必要
としていない機能まで実装しなければならない
n  役割毎に複数のインターフェースに分離すること
n  関連性のあるインターフェースはグループ化して抽象クラスに実装を
まとめること
23
ModemImplA
<<interface>>
DataChannel
+ send(char)
+ recv()
<<interface>>
Connection
+ dial(String)
+ hangup()
DataChannelImpl
ConnectionImpl
<<abstract>>
Modem
+ dial(String)
+ hangup()
+ send(char)
+ recv()
Modemインターフェースは作
らず、DataChannelと
Connectionに分離
関連性が強い機能
は抽象クラスとし
てまとめる
ModemImplB
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
もう⼀度、オブジェクト指向エクササイズ
n  オブジェクト指向の良さを⽣み出す9つのルール
1.  1つのメソッドにつきインデントは1段階までにすること
2.  else句を使⽤しないこと
3.  すべてのプリミティブ型と⽂字列型をラップすること
4.  1⾏につきドットは1つまでにすること
5.  名前を省略しないこと
6.  すべてのエンティティを⼩さくすること
7.  1つのクラスにつきインスタンス変数は2つまでにすること
8.  ファーストクラスコレクションを使⽤すること
9.  Getter/Setter/プロパティを使⽤しないこと
24
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
もう⼀度、オブジェクト指向エクササイズ
n  オブジェクト指向の良さを⽣み出す9つのルール
1.  1つのメソッドにつきインデントは1段階までにすること
2.  else句を使⽤しないこと
3.  すべてのプリミティブ型と⽂字列型をラップすること
4.  1⾏につきドットは1つまでにすること
5.  名前を省略しないこと
6.  すべてのエンティティを⼩さくすること
7.  1つのクラスにつきインスタンス変数は2つまでにすること
8.  ファーストクラスコレクションを使⽤すること
9.  Getter/Setter/プロパティを使⽤しないこと
25
全てのルールは以下のような⽬的を達成するための⼿段
①コードの可読性を向上
②⾼凝集と疎結合の促進による保守性・拡張性・再利⽤性を向上
全てのルールを守ることをゴールにするのではなく、⽬的を理解して
現実と折り合いを付けながら改善することが⼤切
Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved.
参考⽂献
n  ThoughtWorksアンソロジー
•  https://www.amazon.co.jp/ThoughtWorks%E3%82%A2%E3%83%B3%E3%82%BD%E3%83%AD
%E3%82%B8%E3%83%BC-%E2%80%95%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB
%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C
%87%E5%90%91%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BD
%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A4%E3%83%8E
%E3%83%99%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-ThoughtWorks-Inc/dp/487311389X
n  Clean Code
•  https://www.amazon.co.jp/Clean-Code-
%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD
%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%81%94%E4%BA%BA
%E3%81%AE%E6%8A%80-Robert-Martin/dp/4048676881
n  アジャイルソフトウェア開発の奥義
•  https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB
%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B
%E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA
%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C
%87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB
%84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC
%E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/
4797347783
26
キレイなコードの書き方

More Related Content

Similar to キレイなコードの書き方

Software Development with Symfony
Software Development with SymfonySoftware Development with Symfony
Software Development with Symfony
Atsuhiro Kubo
 
PHPフレームワーク入門
PHPフレームワーク入門PHPフレームワーク入門
PHPフレームワーク入門
Sho A
 
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
Akira Inoue
 
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
Akira Inoue
 
AngulaとElixirの新しい関係
AngulaとElixirの新しい関係AngulaとElixirの新しい関係
AngulaとElixirの新しい関係
陸 谷出
 

Similar to キレイなコードの書き方 (20)

Software Development with Symfony
Software Development with SymfonySoftware Development with Symfony
Software Development with Symfony
 
DSLによる要求獲得でスーパーアジャイル
DSLによる要求獲得でスーパーアジャイルDSLによる要求獲得でスーパーアジャイル
DSLによる要求獲得でスーパーアジャイル
 
PHPフレームワーク入門
PHPフレームワーク入門PHPフレームワーク入門
PHPフレームワーク入門
 
flow による型のある世界入門
flow による型のある世界入門flow による型のある世界入門
flow による型のある世界入門
 
Mysql toranomaki
Mysql toranomakiMysql toranomaki
Mysql toranomaki
 
実践 NestJS
実践 NestJS実践 NestJS
実践 NestJS
 
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ ~ Any browser. Any host. Any OS. Open Source. ~
 
JSX 速さの秘密 - 高速なJavaScriptを書く方法
JSX 速さの秘密 - 高速なJavaScriptを書く方法JSX 速さの秘密 - 高速なJavaScriptを書く方法
JSX 速さの秘密 - 高速なJavaScriptを書く方法
 
Service Cloud Trailblazers #5
Service Cloud Trailblazers #5Service Cloud Trailblazers #5
Service Cloud Trailblazers #5
 
作文入門
作文入門作文入門
作文入門
 
Klocworkのご紹介
Klocworkのご紹介Klocworkのご紹介
Klocworkのご紹介
 
Das 2015
Das 2015Das 2015
Das 2015
 
Java/Androidセキュアコーディング
Java/AndroidセキュアコーディングJava/Androidセキュアコーディング
Java/Androidセキュアコーディング
 
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
TypeScript ファーストステップ (Rev.2) ~ Any browser. Any host. Any OS. Open Source. ~
 
エンタープライズIT環境での OpenID Connect / SCIM の具体的実装方法 idit2014
エンタープライズIT環境での OpenID Connect / SCIM の具体的実装方法 idit2014エンタープライズIT環境での OpenID Connect / SCIM の具体的実装方法 idit2014
エンタープライズIT環境での OpenID Connect / SCIM の具体的実装方法 idit2014
 
AngulaとElixirの新しい関係
AngulaとElixirの新しい関係AngulaとElixirの新しい関係
AngulaとElixirの新しい関係
 
PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!PHP 2大 web フレームワークの徹底比較!
PHP 2大 web フレームワークの徹底比較!
 
大人の基礎C#【Niigat.NET 2015-10】
大人の基礎C#【Niigat.NET 2015-10】大人の基礎C#【Niigat.NET 2015-10】
大人の基礎C#【Niigat.NET 2015-10】
 
安全なPHPアプリケーションの作り方2013
安全なPHPアプリケーションの作り方2013安全なPHPアプリケーションの作り方2013
安全なPHPアプリケーションの作り方2013
 
Xtext入門
Xtext入門Xtext入門
Xtext入門
 

More from Takuya Kitamura

More from Takuya Kitamura (9)

アジャイルの手法を取り入れたプロジェクトマネジメントの実例
アジャイルの手法を取り入れたプロジェクトマネジメントの実例アジャイルの手法を取り入れたプロジェクトマネジメントの実例
アジャイルの手法を取り入れたプロジェクトマネジメントの実例
 
大手ユーザー企業に入ってマネジメントでやってみたこと
大手ユーザー企業に入ってマネジメントでやってみたこと大手ユーザー企業に入ってマネジメントでやってみたこと
大手ユーザー企業に入ってマネジメントでやってみたこと
 
グローバル空調メーカーによるIoTプラットフォームへの挑戦
グローバル空調メーカーによるIoTプラットフォームへの挑戦グローバル空調メーカーによるIoTプラットフォームへの挑戦
グローバル空調メーカーによるIoTプラットフォームへの挑戦
 
サーバーレスアーキテクチャで実現するグローバル空調IoTプラットフォームへの挑戦
サーバーレスアーキテクチャで実現するグローバル空調IoTプラットフォームへの挑戦サーバーレスアーキテクチャで実現するグローバル空調IoTプラットフォームへの挑戦
サーバーレスアーキテクチャで実現するグローバル空調IoTプラットフォームへの挑戦
 
AngularJSとバックエンドサービスAppPotで作る業務システムハンズオン
AngularJSとバックエンドサービスAppPotで作る業務システムハンズオンAngularJSとバックエンドサービスAppPotで作る業務システムハンズオン
AngularJSとバックエンドサービスAppPotで作る業務システムハンズオン
 
Swiftにおけるclassとstructの使い分けをDDDから考える
Swiftにおけるclassとstructの使い分けをDDDから考えるSwiftにおけるclassとstructの使い分けをDDDから考える
Swiftにおけるclassとstructの使い分けをDDDから考える
 
UXデザイン✕アジャイル✕受託開発
UXデザイン✕アジャイル✕受託開発UXデザイン✕アジャイル✕受託開発
UXデザイン✕アジャイル✕受託開発
 
スマートデバイスSIの落とし穴と適した開発手法とは?
スマートデバイスSIの落とし穴と適した開発手法とは?スマートデバイスSIの落とし穴と適した開発手法とは?
スマートデバイスSIの落とし穴と適した開発手法とは?
 
jOOQの紹介
jOOQの紹介jOOQの紹介
jOOQの紹介
 

キレイなコードの書き方

  • 2. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 今回のコンテキスト n  プログラマ n  Java •  オブジェクト指向 •  静的型付け⾔語(コンパイル⾔語) n  業務システム •  ⻑期間の保守・エンハンス 2
  • 3. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. キレイなコードとは何か? n  誰でも理解コード? n  変更しやすいコード? n  重複が少ないコード? n  無駄な処理が含まれていないコード? n  ちゃんとコメントが書かれているコード? n  会社の規約に沿ったコード? n  真の意味でオブジェクト指向を実践したコード? n  etc… 3
  • 4. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. コード品質の指標 4 指標 説明 可読性(解析性) 他者がソースコードを⾒て処理の内容を理解できる、または処理の内容を解析できること。 保守性 (修正容易性) 予想できなかった変更を、最低限の修正で実現できること。 重複がなく変更箇所を局所化し、他のコンポーネントへの影響を最⼩にすること。 例:不具合の修正やミドルウェアの変更の場合に、修正箇所が少ない 拡張性 事前に想定できる機能の追加や拡張を、既存コードに影響を与えず実現できること。 拡張が想定されるコンポーネントを、既存コードを修正する事無く、差し込みや差し替え可能 な構造になっていること。 例:認証⽅式をDBからLDAPに切り替える際に、LDAP版の認証モジュールをプラグインできる。 再利⽤性 コードの重複がなく、複数存在する同⼀ロジックの処理を1つのモジュールで実現しているこ と。 コードが再利⽤可能な粒度で汎⽤的なモジュールに分割されていること。 運⽤性 障害発⽣時にトラブルシューティングしやすい状態にあること。 環境依存のパラメータをプログラムを変更する事無く切り替えられる仕組みとなっていること。 その他の指標 上述以外の品質指標。 それぞれの品質指標を⾼い⽔準で満たしている事。 n  よく使われるコードの品質を測るための指標
  • 5. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. キレイなコードを書くためには… n  キレイなコードであると判断する基準はいくつもある n  個々の基準を満たすためにそれぞれのアプローチがある n  キレイなコードの定義を⼀⾔で⾔い表す事は難しく、実践 ⽅法も⼀筋縄でいかない n  この状況をわかりやすく表すために、キレイなコードを書 くためには「センス」が必要だと表現される n  このキレイさの「センス」を⾝につけるには、無数の⼩さ な⼿法を知識として蓄え、⼀つ⼀つ丁寧に実践していく必 要がある 5
  • 6. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. ⼀般的に⾏われているアプローチを学ぶ n  今⽇は以下の書籍の中で語られている「キレイなコードを 書く」ための代表的なアプローチを学ぶ •  時間が限られているので、あくまでも⼀部のみ 6
  • 7. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 7
  • 8. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 基礎編 8
  • 9. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 意味のある名前をつける n  変数、関数、引数、クラス、パッケージの全てにおいて、注意深く、意図が 明確な名前を付ける n  偽や曖昧な名前を避け、コード中での意味が変わったら、速やかに意味に合 わせた名前に変える n  エンコーディングが必要な名前にしない n  クラス名には名詞を、メソッド名には動詞を使う n  システム内で同じ抽象概念、コンセプトには同じ命名規則を適⽤して⼀貫性 を持たせる n  ビジネスドメインの⽤語を使⽤する n  多少⻑くなっても意図を理解できる名前を付ける事を優先する            9 public List<int[]> getThem() { List<int[]> list1 = new ArrayList<>(); for (int[] x : theList) if (x[0] == 4) list.add(x); return list1; } public List<int[]> getFlaggedCells() { List<int[]> flaggedCells = new ArrayList<>(); for (int[] cell : gameBoard) if (cell[STATUS_VALUE] == FLAGGED) flaggedCells.add(cell); return flaggedCells; }
  • 10. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 関数を⼩さく保つ n  ⼀にも⼆にも関数は⼩さく保つこと n  1つの関数は1つの事だけをきちんと⾏い、それ以外の事 を⾏ってはならない n  1つの関数は1つの抽象レベルにする •  抽象度の異なる処理が1つの関数内に混在しないようにする n  DRY(Don't Repeat Yourself)原則を守る •  重複しているコードは、それだけで1つの役割を持っている 10
  • 11. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 11 public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean includeSuiteSetup) throws Exception { WikiPage wikiPage = pageData.getWikiPage(); StringBuilder buffer = new StringBuilder(); if (pageData.hasAttribute("Test")) { if (includeSuiteSetup) { WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage(SUITE_SETUP_NAME, wikiPage); if (suiteSetup != null) { WikiPagePath pagePath = suiteSetup.getPageCrawler().getFullPath(suteSetup); String pagePathName = PathParser.render(pagePath); buffer.append("!include -setup .") .append("pagePathName") .append("n"); } } WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage); if (setup != null) { WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup); String setupPathName = PathParser.render(setupPath); buffer.append("!include -setup .") .append(setupPathName) .append("n"); } } buffer.append(pageData.getContent()); if(pageData.hasAttribute("Test")) { WikiPage teardown = PageCrawlerImpl.getInheritedPage("TearDown", wikiPage); if (teardown != null) { WikiPagePath tearDownPath = suiteSetup.getPageCrawler().getFullPath(teardown); String tearDownPathName = PathParser.render(tearDownPath); buffer.append("n") .append("!include -teardown .") .append("tearDownPathName") .append("n"); } if (includeSuiteSetup) { WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(SUITE_TEARDOWN_NAME, wikiPage); if (suiteTeardown != null) { WikiPagePath pagePath = suiteTeardown.getPageCrawler().getFullPath(suiteTeardown); String pagePathName = PathParser.render(pagePath); buffer.append("!include -teardown .") .append("pagePathName") .append("n"); } } } pageData.getContent(buffer.toString()); return pageData.getHtml(); } public static String renderPageWithSetupsAndTeardowns( PageData pagaData, boolean isSuite) throws Exception { boolean isTestPage = pageData.hasAttribute("Test"); if (isTestPage) { WikiPage testPage = pageData.getWikiPage(); StringBuilder newPageContent = new StringBuilder(); includeSetupPages(testPage, newPageContent, isSuite); newPageContent.append(pageData.getContent()); includeTeardownPages(testPage, newPageContent, isSuite); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); } public static String renderPageWithSetupsAndTeardowns( PageData pagaData, boolean isSuite) throws Exception { if (isTestPage(pageData)) { includeSetupAndTeardownPages(pageData, isSuite); } return pageData.getHtml(); } リファクタリング後 さらにリファクタリング後
  • 12. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 副作⽤を避ける n  副作⽤とは、関数が1つのことを⾏なうことを保証しつつ、隠れ て別のことを⾏なうこと n  誤解を招き、奇妙な時間軸の関連を産み、依存関係の順序を狂わ せる n  出⼒引数を避ける n  コマンド・照会の分離原則を守る •  関数では、何らかの処理を⾏なうか、何らかの応答を返すかのどちらかを⾏ うべきで、両⽅を⾏ってはならない 12 public class UserValidator { private Cryptographer cryptographer; public boolean checkPassword(String userName, String password) { User user = UserGateway.findByName(userName); if (user != User.NULL) { String codedPhrase = user.getPhraseEncodedByPassword(); String phrase = crytographer.decrypt(codedPhrase, password); if ("Valid Password".equals(phrase)) { Session.initialize(); return true; } } return false; } }
  • 13. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. コメント n  コメントは必要悪 •  あくまで、コードでうまく表現することに失敗したときに、それを補うのに使う n  コメントは間違っていても動いてしまう •  コードは正しく修正されても、コメントは置き去りになりがち •  コメントはコンパイルされず、テスト出来ないことを意識する n  コードの意図は、コード⾃⾝で伝える •  変数名や関数名を利⽤してコードに意図を込める n  よいコメント •  公開APIにおけるJavaDoc •  実装の背景や決定要因を伝えるコメント 13 // 従業員が、給与の完全給付をうけるかどうかをチェック if((employee.flags & HOURLY_FLAG) && (employee.age > 65)) if(emplyee.isEligibleForFullBenefits()) 悪い例 良い例
  • 14. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. オブジェクト指向編 14
  • 15. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 凝集度と結合度 n  凝集度 •  クラスやパッケージ内の機能要素と情報要素間の関連性の強さを表す指標 •  互いに関連する機能や情報があちこちに分散していると、仕様変更が⽣じた 場合の影響範囲が広くなってしまう •  凝集度を⾼めようとすると1つのクラスが持つデータの数がへり、⼩さなク ラスが⼤量にできるようになる n  結合度 •  クラスやパッケージ間で、呼び出し関係にあるメソッドの結び付きの強さを 表す指標 •  結合度が⾼くなると複数のクラスやパッケージ間で依存度が上がってしまう ため、保守やメンテナンス、仕様変更などの対応がしづらくなる n  凝集度を⾼め、結合度を低くする事で、保守性や拡張性を⾼める 事が必要 15
  • 16. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. データとロジックの置き場所を同じクラスにする n  オブジェクトは裏にあるデータを隠して抽象化し、データを操作 する機能を公開するためのもの •  全てのデータにsetter/getterを⽤意すると本末転倒 n  データ構造の変更が必要になった場合でも、⾃クラス内の修正に 留める事ができる n  デメテルの法則を守る・守らせる •  オブジェクトを使⽤する場合、そのオブジェクトの内部についてしるべきで はない •  データを隠蔽し、操作を公開する •  必然的に引数の受け渡しの数が少なくなる n  データ転送オブジェクトの利⽤を避ける •  レイヤ設計に伴って仕⽅がない場合にのみ利⽤ 16
  • 17. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 多態性を利⽤する n  多態性とは、実⾏される処理の実体が、コールされたメッセージ ではなく、メッセージを受けたオブジェクトによって決定される 性質 •  呼び出し側は、処理を依頼した側の実体を知らなくてよい n  多態性を活⽤する事により分岐処理を無くす •  多態性を使って分岐した場合は、新しい分岐候補が追加されても、呼び出し 側の修正が不要 •  保守性や拡張性を上げる事ができる 17 Shape float calcTotalArea(List<Shape> shapes) { float totalArea = 0.0; for (shape in shapes) { totalArea += shape.getArea(); } return totalArea; } + getArea() Rectangle + getArea() Circle + getArea() Shapeにのみ 依存する
  • 18. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. SOLID原則を⽤いた設計 n  オブジェクト指向の設計における5つの基本原則 •  単⼀責務の原則(SRP) •  オープン・クローズドの原則(OCP) •  リスコフの置換原則(LSP) •  インターフェース分離の原則(ISP) •  依存関係逆転の原則(DIP) 18
  • 19. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 単⼀責務の原則(SRP) n  「クラスを変更する理由は1つより多く存在してはならない」 n  複数の変更理由が存在するクラスは改修の過程で凝集度が下がっ てしまい、変更に弱くなる •  1つの役割の変更が、別の役割の部分にまで影響を及ぼしてしまう n  責務や役割と捉えると判断が難しいため、「変更の理由」と捉え て考えてみる 19 Modem + dial(String) + hangup() + send(char) + recv() DataChannel + send(char) + recv() Connection + dial(String) + hangup() Modem 責務の分割
  • 20. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. オープン・クローズドの原則(OCP) n  「ソフトウェアの構成要素は、拡張に対して開いていて、修正に対し て閉じていなければならない」 n  オープン:拡張に対して開かれている •  アプリの仕様が変更されても、モジュールに新たな振る舞いを追加するだけでその変 更に対処できる n  クローズド:修正に対して閉じている •  モジュールの振る舞いを拡張しても、他のモジュールには影響が出ない、コンパイル なども不要な状態 n  多態性をうまく活⽤する 20 Client Server Client <<interface>> Server ServerImpl ClientはServerを参照している ため、Serverが変更されると Clientも何らかの変更、または 再コンパイルが必要 Clientはinterfaceを参照してい るだけなので、ServerImplの 振る舞いが修正されても変更 もコンパイルも不要
  • 21. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. リスコフの置換原則(LSP) n  「派⽣型はその基本型と置換可能でなければならない」 •  Tから派⽣したSがあり、Tを参照している任意の処理をSに置き換えても問 題なく動作する事を保証しなければならない n  例えば、派⽣クラスで親クラスのメソッドをオーバーライドして 振る舞いを壊している、例外を投げているなど n  または、利⽤クラス側が多態性を無視して具象クラスにキャスト して利⽤している n  この原則はコンパイルの仕組だけでは保証できない n  親と⼦でメソッド毎に事前条件、事後条件を取り決めてそれを守 るようにする •  そもそも、できるだけ前後の処理に依存しない形でインターフェースを設計 する事 21
  • 22. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 依存関係逆転の原則(DIP) n  「上位のモジュールは下位のモジュールに依存してはならない。どちらのモジュール も"抽象"に依存すべきである。」 n  「"抽象"は実装の詳細に依存してはならない。実装の詳細が"抽象"に依存すべきであ る。」 n  上位(⽅針を決めるレイヤ)が下位(具体的な実装レイヤ)に依存すると、実装の変更 が⽅針に影響を及ぼしてしまうため n  多態性をうまく活⽤する n  レイヤ毎に⾼い凝集性を保つ必要がある 22 Policy Layer Mechanism Layer Utility Layer Policy Layer <<interface>> Policy Service Mechanism Layer Mechanism Layer Utility Layer Policy Policy Policy 悪い例 良い例
  • 23. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. インターフェース分離の原則(ISP) n  「クライアントに、クライアントが利⽤しないメソッドへの依存を強 制してはならない」 n  太ったインターフェースを作らないこと n  様々な⽤途を持った1つのインターフェースを作ると、実装側が必要 としていない機能まで実装しなければならない n  役割毎に複数のインターフェースに分離すること n  関連性のあるインターフェースはグループ化して抽象クラスに実装を まとめること 23 ModemImplA <<interface>> DataChannel + send(char) + recv() <<interface>> Connection + dial(String) + hangup() DataChannelImpl ConnectionImpl <<abstract>> Modem + dial(String) + hangup() + send(char) + recv() Modemインターフェースは作 らず、DataChannelと Connectionに分離 関連性が強い機能 は抽象クラスとし てまとめる ModemImplB
  • 24. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. もう⼀度、オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 24
  • 25. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. もう⼀度、オブジェクト指向エクササイズ n  オブジェクト指向の良さを⽣み出す9つのルール 1.  1つのメソッドにつきインデントは1段階までにすること 2.  else句を使⽤しないこと 3.  すべてのプリミティブ型と⽂字列型をラップすること 4.  1⾏につきドットは1つまでにすること 5.  名前を省略しないこと 6.  すべてのエンティティを⼩さくすること 7.  1つのクラスにつきインスタンス変数は2つまでにすること 8.  ファーストクラスコレクションを使⽤すること 9.  Getter/Setter/プロパティを使⽤しないこと 25 全てのルールは以下のような⽬的を達成するための⼿段 ①コードの可読性を向上 ②⾼凝集と疎結合の促進による保守性・拡張性・再利⽤性を向上 全てのルールを守ることをゴールにするのではなく、⽬的を理解して 現実と折り合いを付けながら改善することが⼤切
  • 26. Copyright ©2017/01/05, NC Design & Consulting Co., Ltd. All rights reserved. 参考⽂献 n  ThoughtWorksアンソロジー •  https://www.amazon.co.jp/ThoughtWorks%E3%82%A2%E3%83%B3%E3%82%BD%E3%83%AD %E3%82%B8%E3%83%BC-%E2%80%95%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB %E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C %87%E5%90%91%E3%81%AB%E3%82%88%E3%82%8B%E3%82%BD %E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%A4%E3%83%8E %E3%83%99%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3-ThoughtWorks-Inc/dp/487311389X n  Clean Code •  https://www.amazon.co.jp/Clean-Code- %E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD %E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%81%94%E4%BA%BA %E3%81%AE%E6%8A%80-Robert-Martin/dp/4048676881 n  アジャイルソフトウェア開発の奥義 •  https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB %E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B %E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA %E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C %87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB %84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC %E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/ 4797347783 26