新卒の時に作ったJavaのシステムをいじって学ぶシリーズ(Builderパターン)
はじめに
新卒で社内の目標管理系のwebアプリを作ったのですが、なにせJava覚えたてPlay framework覚えたてで
かなりコードの見通しの悪い部分やリファクタしがいのある部分があります。
ここは恥を忍んでリファクタを晒しながらEffective Javaの内容や
デザインパターンに書かれている内容に実践的に触れていこうと思いました。
拙いコードですが実際のリファクタをするとここまでコードの可読性が上がったりするんか!というのを初心者には実感していただきたいです。
また中級者の方達とはこの部分はこうした方がええぞみたいなマサカリを期待しております。
前置きはこの辺にして本題へGO!
今回のいじる箇所
今回はCSVを生成する部分のコードを直していこうと思います。
それでは直す前のコードを見てみましょう。
自分だったらどこを直すか考えてみてください。
public List<Csv> getEvaluations(String gaUserId, Integer year, String term) throws IOException {
AlibabaApi alibabaApi = AlibabaApiHelper.getAlibabaRet();
List<User> subordinates = alibabaApi.getSubordinates(gaUserId).execute().body().users;
List<User> mySubordinates = (subordinates.stream().filter(distinctByKey(p -> p.gaUserId)).collect(Collectors.toList()));
List<UserTerm> list = mySubordinates.stream().map(user -> termTargetService.getUserTerm(user.gaUserId,year,term)).collect(Collectors.toList());
List<Csv> allSubordinatesCsv = list.stream()
.map(user -> new Csv(
user.name,
user.grade,
user.termTargets.target == null ? "" : user.termTargets.target.body,
user.termTargets.target == null ? "" : user.termTargets.target.selfComments == null ? "" : user.termTargets.target.selfComments.quarter == null ? "" : user.termTargets.target.selfComments.quarter.body,
user.termTargets.contribution == null ? "" : user.termTargets.contribution.body,
user.termTargets.contribution == null ? "" : user.termTargets.contribution.selfComments == null ? "" : user.termTargets.contribution.selfComments.quarter == null ? "" : user.termTargets.contribution.selfComments.quarter.body,
user.termTargets.superiorComments == null ? "" : user.termTargets.superiorComments.quarter == null ? "" : user.termTargets.superiorComments.quarter.body)
).collect(Collectors.toList());
return allSubordinatesCsv;
}
はっきり言いましょう。ひどいです。
このCSVは個人の目標(月ごと)と目標に対する振り返りをマネジメント配下にいる人数分出すことを目的としています。
具体的にどこが問題かというと
new Csv()している箇所の引数が多いため非常に見通しが悪いという点にあります。
また現場都合なのですが、このCSVの項目は今後も増える可能性が大いにあり現在のままでは項目が増えた際にさらに読みにくく
メンテ性の悪いコードになってしまいます。
Builderパターンとは
そこでEffective Javaから知恵を拝借しました。
今回使うのはBuilderパターンです。
Builderパターンは通常ならnewしてインスタンスを生成するとこを
staticなクラスの中にBuilderメソッドを用意し、フィールドの値をセットします。
Builderパターンを用いるメリットはコンストラクタに渡すパラメーターの見通しが格段によくなる点です。
また不要なパラメータがいる場合ならセットしなければいいだけなので実装の拡張性を担保できます。
実際に何をやっていくかは下の項で!
ちなみに変数の名前は都合により変えています。
berfor after
さて実際にリファクタリングしてみましょう。
まずはcsvを作る時のmodelclassをリファクタリングして行きましょう。
befor
public class Csv {
/**
* 名前
*/
public String name;
/**
* 役職
*/
public String yakushoku;
/**
* 目標
*/
public String mokuhyou;
/**
* 目標に対する振り返り
*/
public String hurikaeri;
/**
* 目標に対する上司のコメント
*/
public String joushinoKoment;
/**
* 組織貢献のための目標
*/
public String sosikikoukenMokuhyou;
/**
* 組織貢献目標の振り返り
*/
public String sosikikoukenMokuhyoHurikaeri;
public Csv(String name, String yakushoku, String mokuhyou, String hurikaeri, String joushinoKoment, String sosikikoukenMokuhyou,String sosikikoukenMokuhyoHurikaeri){
this.name = name;
this.grade = yakushoku;
this.target = mokuhyou;
this.hurikaeri = hurikaeri;
this.joushinoKoment = joushinoKoment;
this.sosikikoukenMokuhyou = sosikikoukenMokuhyou;
this.sosikikoukenMokuhyoHurikaeri = sosikikoukenMokuhyoHurikaeri;
}
}
after
public class Csv {
/**
* 名前
*/
public String name;
/**
* 役職
*/
public String yakushoku;
/**
* 目標
*/
public String mokuhyou;
/**
* 目標に対する振り返り
*/
public String hurikaeri;
/**
* 目標に対する上司のコメント
*/
public String joushinoKoment;
/**
* 組織貢献のための目標
*/
public String sosikikoukenMokuhyou;
/**
* 組織貢献目標の振り返り
*/
public String sosikikoukenMokuhyoHurikaeri;
private Csv() {
}
public static class Builder {
String name = "";
String yakushoku = "";
String mokuhyou = "";
String hurikaeri = "";
String joushinoKoment ="";
String sosikikoukenMokuhyou = "";
String sosikikoukenMokuhyoHurikaeri = "";
public Builder name(String name) {
this.name = name;
return this;
}
public Builder yakushoku(String yakushoku) {
this.yakushoku = yakushoku;
return this;
}
public Builder mokuhyou(String mokuhyou) {
this.mokuhyou = mokuhyou;
return this;
}
public Builder hurikaeri(String hurikaeri) {
this.hurikaeri = hurikaeri;
return this;
}
public Builder joushinoKoment(String joushinoKoment) {
this.joushinoKoment = joushinoKoment;
return this;
}
public Builder sosikikoukenMokuhyou(String sosikikoukenMokuhyou){
this.sosikikoukenMokuhyou = sosikikoukenMokuhyou;
return this;
}
public Builder sosikikoukenMokuhyoHurikaeri(String sosikikoukenMokuhyoHurikaeri) {
this.sosikikoukenMokuhyoHurikaeri = sosikikoukenMokuhyoHurikaeri;
return this;
}
public Csv build() {
Csv csv = new Csv();
csv.name = this.name;
csv.yakushoku = this.yakushoku;
csv.mokuhyou = this.mokuhyou;
csv.hurikaeri = this.hurikaeri;
csv.joushinoKoment = this.joushinoKoment;
csv.sosikikoukenMokuhyou = this.sosikikoukenMokuhyou;
csv.sosikikoukenMokuhyoHurikaeri = this.sosikikoukenMokuhyoHurikaeri;
return csv;
}
}
}
具体的にやったことはCsv型のbuilderメソッドを用意しました。
そしてリファクタがうまく行くと実際のCSVの生成部分はこうなります。
private List<Csv> transformIntoCSV(List<UserTerm> list) {
return list.stream()
.map(user -> new Csv.Builder()
.name(user.name)
.grade(user.grade)
.target(user.termTargets.target == null ? "" : user.termTargets.target.body)
.commentForTarget(user.termTargets.target == null ? "" : user.termTargets.target.selfComments == null ? "" : user.termTargets.target.selfComments.quarter == null ? "" : user.termTargets.target.selfComments.quarter.body)
.contribution(user.termTargets.contribution == null ? "" : user.termTargets.contribution.body)
.commentForContribution(user.termTargets.contribution == null ? "" : user.termTargets.contribution.selfComments == null ? "" : user.termTargets.contribution.selfComments.quarter == null ? "" : user.termTargets.contribution.selfComments.quarter.body)
.superiorCommentForTarget(user.termTargets.superiorComments == null ? "" : user.termTargets.superiorComments.quarter == null ? "" : user.termTargets.superiorComments.quarter.body)
.build()
).collect(Collectors.toList());
}
だいぶコードの見通しがよくなりメンテのしやすいコードになりました。
まとめ
今回はEffective Javaに登場するBuilderパターンを用いて
可読性が悪くメンテしずらいCSVの生成部分をリファクタしてみました。
ポイントとしてはBuilderパターンを用いることで引数が増えた場合でも可読性が高くなり
CSVの項目の見通しがよくなる点です。
Builderパターンは今回のように要件が変更されやすく、多くの引数を必要とするメソッドを扱う上で非常に有効なことがわかっていただけたかと思います。