初めに
「現場で役立つシステム設計の原則」を読んでいろいろな知見を得ることができました。そこで特に参考になり、既に業務で活かしている内容かつ比較的すぐに実践できる内容を紹介させていただこうと思います。ご参考になれば幸いです!
1. 空白行や説明変数を利用する
(悪い例)区切りがはっきりしないコードと破壊的代入を行っている変数price
int price = drinkPrice + foodPrice;
if(price < 1000){
price -= 100 // 割引
}
price *= taxRate();
上記のコードでは二点改善できる箇所があります。
1.空白行を利用してコードの境目を明確化する
上記のコードでは「基本の価格の計算」&「割引加算」&「税額の加算」を実行していますが、区切りがない為どこまでが何の計算であるかが明確ではありません。意味が異なる場所を見つけたら、空白行を加えた方が可読性を高めることができます。
2.説明用変数を導入する
上記のコードではprice
が色々な意味を持ってしまっています。そのため、目的毎に専用の変数を用意することで結合度が弱くなり、独立性を向上させることができます。
また、price
は何度も再代入されています。このような1つの変数を使い回して代入を繰り返すやり方を破壊的代入といいます。
加えて、変数名はとても重要です。適切な変数名をつけていた場合、その変数が何を指しているを直感的に理解することが出来ます。(変数名やメソッド名が適切でないとコメントで補足するなどがよく見られます。)
改善したコードは下記の様になります。
int basePrice = drinkPrice + foodPrice;
int discountPrice = 0; //割引価格の初期化
if(basePrice < 1000){
discountPrice = 100 //説明用変数
}
int itemPrice = (basePrice - discountPrice) * taxRate();
2.メソッドに抽出する
(悪い例)判断や処理のロジックをそのままif文の中に書く
if(foodName.equeals("Ramen")){
discountPrice = 1000;
}
メソッドとして抽出することで下記の様なメリットがあります
- メソッド名を読むだけで何をしたいのかを読み取ることができる
- コードが整理される
- 条件を変えたい場合はメソッド内を変更すれば良いため、変更が楽になる
- if文の中に限らず、なるべく目的毎にメソッド化した方が疎結合に成ることに加えて、どこに何が書いてあるか分かりやすくなります。
改善したコードは下記の様になります。
if(isRamen()){
discountPrice = 1000;
}
/**
* 商品がラーメンかを判定する
*/
private boolean isRamen(){
return foodName.equals("Ramen");
}
3.ドメインオブジェクトを利用する
(悪い例)色々な箇所に判定ロジックが散らばっている例
下記の様なFoodItemクラスがあるとします。(よくある一般的なEntityクラスだと思います。)
class FoodItem(){
String itemName;
int itemPrice;
public String getItemName(){
return itemName
}
public void setItemName(String itemName){
this.itemName = itemName;
}
public int getItemPrice(){
return itemPrice;
}
}
加えて、FoodItem
クラスを利用するCalculateItem
クラスがあるとします。
class CalculateItem() {
public FoodItem calculate(){
FoodItem foodItem = new FoodItem();
if(foodItem.getItemPrice > 3000){
final String foodName = foodItem.getItemName;
foodItem.setItemName("送料無料!!" + foodName);
}
return foodItem;
}
}
ここでは、foodPrice
が3000円を超えていた場合に、「送料無料」というPrefixをつけています。
ここでは何が問題かというと、3000円を超えていた場合の処理を利用する側(calCulateItem
)で書いているのが問題ということになります。
これ位だと、別にこれくらいいいじゃん!と思いますが、これが肥大化するとビジネスロジックだらけの神クラスになり、どこに何の処理が書いてあるのかが分からなくなってしまいます。業務レベルのコードだとあるあるなのでは無いでしょうか。
また、foodItem
を利用するクラスが新しく出来たとします。その際に3000円を超えた場合の処理を書きたくなった場合は、再度そちらのクラスに書く必要があります。その上、3000円という数値を4000円に変更したい場合は、2つのクラスを修正する必要があります。
この様な形でビジネスロジックを管理する、謂わゆるサービスクラスは肥大化し、最悪の場合カオスな神クラスになってしまいます。
下記の様に修正することで、改善が見込まれます。
class FoodItem(){
int baseDiscountPrice = 3000;
String itemName;
int itemPrice;
public String getItemName(){
return itemName
}
Public void setItemName(String itemName){
this.itemName = itemName;
}
public int getItemPrice(){
return itemPrice;
}
/**
* 商品が割引対象かを判定する
*/
public boolean isDiscountTarget(){
return itemPrice < baseDiscountPrice;
}
}
class CalculateItem() {
public FoodItem calculate(){
FoodItem foodItem = new FoodItem();
if(foodItem.idDiscountTarget()){
final String foodName = foodItem.getItemName;
foodItem.setItemName("送料無料!!" + foodName);
}
return foodItem;
}
}
上記の例では、データを持っているクラスにロジックを移動しました。その結果として、ロジックがどこに書いてあるかが分かりやすくなり、修正を楽で安全にすることが出来ます。今回はかなり簡単な例で紹介しましたが、こちらを応用することで色々なビジネスロジックをデータクラス側に移動することが可能になり、利用する側(サービスクラス)がかなりスッキリします。肝は、データを用いた処理はデータを持っているクラスで処理するという点にあると思います。
このように、データとロジックを一つにまとめたオブジェクトをドメインオブジェクトと呼んでいます。
まとめ
本を読んで、大切であり、比較的楽に導入可能な内容を記載しました。当たり前ですが、本にはより詳細なシステム設計の原則が書いてあります。ここでは紹介しきれなかった重要な内容も記載されていますので、一度読んでみることをお勧めします!(特に初心者エンジニアの方はとても為になる内容かと思います)
最後までお読みいただきありがとうございました!