Java関数の優雅な道(上)
44641 ワード
リード
ソフトウェアプロジェクトコードの蓄積に伴い、システムのメンテナンスコストがますます高くなり、すべてのソフトウェアチームが直面している共通の問題です.コードを持続的に最適化し、コードの品質を高めることは、システムの生命力を向上させる有効な手段の一つである.ソフトウェアシステム思考には「Less coding,more thinking(符号化・多思考)」という言葉があり、「Think more,code less(思考が多ければ多いほど符号化が少ない)」というスラングもある.だから、私たちは符号化の中で多く考えて、自分の符号化レベルを高めるように努力してこそ、より優雅で、より質が高く、より効率的なコードを書くことができます.
本文はJava関数に関連する符号化規則をまとめ、多くのJavaプログラマーにいくつかの符号化提案を提供し、より優雅で、より質が高く、より効率的なコードを作成するのに役立つことを目的としています.このコード規則は、高徳採集部門での実践を通じて、すでに良い効果を得た.
汎用ツール関数の使用
ケース1
現象の説明:
不完全な書き方:
thisName != null && thisName.equals(name);
より完璧な書き方:
(thisName == name) || (thisName != null && thisName.equals(name));
推奨事項:
Objects.equals(name, thisName);
ケース2
現象の説明:
!(list == null || list.isEmpty());
推奨事項:
import org.apache.commons.collections4.CollectionUtils;
CollectionUtils.isNotEmpty(list);
主な収益
スーパー関数の分割
1つの関数が80行を超えると、超大関数に属し、分割する必要があります.
ケース1:コードブロックごとに1つの関数にカプセル化できます
各コードブロックには、このコードブロックの機能を説明するためのコメントが必ずあります.
コードブロックの前にコメントがある場合は、このコードを関数に置き換えることができ、コメントに基づいてこの関数に名前を付けることができます.関数に適切な名前がある場合は、内部コードがどのように実現されているかを見る必要はありません.
現象の説明:
//
public void liveDaily() {
//
//
//
//
//
//
}
推奨事項:
//
public void liveDaily() {
//
eat();
//
code();
//
sleep();
}
//
private void eat() {
//
}
//
private void code() {
//
}
//
private void sleep() {
//
}
ケース2:各サイクルは1つの関数にカプセル化できます
現象の説明:
//
public void live() {
while (isAlive) {
//
eat();
//
code();
//
sleep();
}
}
推奨事項:
//
public void live() {
while (isAlive) {
//
liveDaily();
}
}
//
private void liveDaily() {
//
eat();
//
code();
//
sleep();
}
ケース3:各条件体は1つの関数にカプセル化できます
現象の説明:
//
public void goOut() {
//
// :
if (isWeekday()) {
//
}
// :
else {
//
}
}
推奨事項:
//
public void goOut() {
//
// :
if (isWeekday()) {
play();
}
// :
else {
work();
}
}
//
private void play() {
//
}
//
private void work() {
//
}
主な収益
同じ関数内のコードブロックレベルができるだけ一致するようにする
ケース1
現象の説明:
//
public void liveDaily() {
//
eat();
//
code();
//
//
}
明らかに、寝るというコードブロックはeat(食事)とcode(コード)と同じレベルではなく、突兀に見えます.コードを書くことを文章を書くことにたとえると、eat(食事)とcode(コード)は段落の大意であり、寝るというコードブロックは詳細な段落に属する.liveDaily(毎日の生活)という関数では,主な流れ(段落の大意)を書くだけでよい.
推奨事項:
public void liveDaily() {
//
eat();
//
code();
//
sleep();
}
//
private void sleep() {
//
}
主な収益
同じ機能コードを関数としてカプセル化
ケース1:同じコードを関数としてカプセル化
現象の説明:
//
public void disableUser() {
//
List userIdList = queryBlackUser();
for (Long userId : userIdList) {
User userUpdate = new User();
userUpdate.setId(userId);
userUpdate.setEnable(Boolean.FALSE);
userDAO.update(userUpdate);
}
//
userIdList = queryExpiredUser();
for (Long userId : userIdList) {
User userUpdate = new User();
userUpdate.setId(userId);
userUpdate.setEnable(Boolean.FALSE);
userDAO.update(userUpdate);
}
}
推奨事項:
//
public void disableUser() {
//
List userIdList = queryBlackUser();
for (Long userId : userIdList) {
disableUser(userId);
}
//
userIdList = queryExpiredUser();
for (Long userId : userIdList) {
disableUser(userId);
}
}
//
private void disableUser(Long userId) {
User userUpdate = new User();
userUpdate.setId(userId);
userUpdate.setEnable(Boolean.FALSE);
userDAO.update(userUpdate);
}
ケース2:類似コードを関数としてカプセル化
パッケージ類似コードは関数であり,差異は関数パラメータによって制御される.
現象の説明:
//
public void adoptOrder(Long orderId) {
Order orderUpdate = new Order();
orderUpdate.setId(orderId);
orderUpdate.setStatus(OrderStatus.ADOPTED);
orderUpdate.setAuditTime(new Date());
orderDAO.update(orderUpdate);
}
//
public void rejectOrder(Long orderId) {
Order orderUpdate = new Order();
orderUpdate.setId(orderId);
orderUpdate.setStatus(OrderStatus.REJECTED);
orderUpdate.setAuditTime(new Date());
orderDAO.update(orderUpdate);
}
推奨事項:
//
public void adoptOrder(Long orderId) {
auditOrder(orderId, OrderStatus.ADOPTED);
}
//
public void rejectOrder(Long orderId) {
auditOrder(orderId, OrderStatus.REJECTED);
}
//
private void auditOrder(Long orderId, OrderStatus orderStatus) {
Order orderUpdate = new Order();
orderUpdate.setId(orderId);
orderUpdate.setStatus(orderStatus);
orderUpdate.setAuditTime(new Date());
orderDAO.update(orderUpdate);
}
主な収益
パッケージ取得パラメータ値関数
ケース1
現象の説明:
//
public boolean isPassed(Long userId) {
//
double thisPassThreshold = PASS_THRESHOLD;
if (Objects.nonNull(passThreshold)) {
thisPassThreshold = passThreshold;
}
//
double passRate = getPassRate(userId);
//
return passRate >= thisPassThreshold;
}
推奨事項:
//
public boolean isPassed(Long userId) {
//
double thisPassThreshold = getPassThreshold();
//
double passRate = getPassRate(userId);
//
return passRate >= thisPassThreshold;
}
//
private double getPassThreshold() {
if (Objects.nonNull(passThreshold)) {
return passThreshold;
}
return PASS_THRESHOLD;
}
主な収益
インタフェースパラメータ化による同一論理のカプセル化
ケース1
現象の説明:
//
public void sendAuditorSettleData() {
List settleDataList = auditTaskDAO.statAuditorSettleData();
for (WorkerSettleData settleData : settleDataList) {
WorkerPushData pushData = new WorkerPushData();
pushData.setId(settleData.getWorkerId());
pushData.setType(WorkerPushDataType.AUDITOR);
pushData.setData(settleData);
pushService.push(pushData);
}
}
//
public void sendCheckerSettleData() {
List settleDataList = auditTaskDAO.statCheckerSettleData();
for (WorkerSettleData settleData : settleDataList) {
WorkerPushData pushData = new WorkerPushData();
pushData.setId(settleData.getWorkerId());
pushData.setType(WorkerPushDataType.CHECKER);
pushData.setData(settleData);
pushService.push(pushData);
}
}
推奨事項:
//
public void sendAuditorSettleData() {
sendWorkerSettleData(WorkerPushDataType.AUDITOR, () -> auditTaskDAO.statAuditorSettleData());
}
//
public void sendCheckerSettleData() {
sendWorkerSettleData(WorkerPushDataType.CHECKER, () -> auditTaskDAO.statCheckerSettleData());
}
//
public void sendWorkerSettleData(WorkerPushDataType dataType, WorkerSettleDataProvider dataProvider) {
List settleDataList = dataProvider.statWorkerSettleData();
for (WorkerSettleData settleData : settleDataList) {
WorkerPushData pushData = new WorkerPushData();
pushData.setId(settleData.getWorkerId());
pushData.setType(dataType);
pushData.setData(settleData);
pushService.push(pushData);
}
}
//
private interface WorkerSettleDataProvider {
//
public List statWorkerSettleData();
}
主な収益
関数コードレベルの削減
関数を美しくするには、関数コードレベルを1~4にすることをお勧めします.インデントが多すぎると、関数が読みにくくなります.
ケース1:returnによる事前戻り関数
現象の説明:
//
public Double getUserBalance(Long userId) {
User user = getUser(userId);
if (Objects.nonNull(user)) {
UserAccount account = user.getAccount();
if (Objects.nonNull(account)) {
return account.getBalance();
}
}
return null;
}
推奨事項:
//
public Double getUserBalance(Long userId) {
//
User user = getUser(userId);
if (Objects.isNull(user)) {
return null;
}
//
UserAccount account = user.getAccount();
if (Objects.isNull(account)) {
return null;
}
//
return account.getBalance();
}
ケース2:continueによる早期終了サイクル
現象の説明:
//
public double getTotalBalance(List userList) {
//
double totalBalance = 0.0D;
//
for (User user : userList) {
//
UserAccount account = user.getAccount();
if (Objects.nonNull(account)) {
//
Double balance = account.getBalance();
if (Objects.nonNull(balance)) {
totalBalance += balance;
}
}
}
//
return totalBalance;
}
推奨事項:
//
public double getTotalBalance(List userList) {
//
double totalBalance = 0.0D;
//
for (User user : userList) {
//
UserAccount account = user.getAccount();
if (Objects.isNull(account)) {
continue;
}
//
Double balance = account.getBalance();
if (Objects.nonNull(balance)) {
totalBalance += balance;
}
}
//
return totalBalance;
}
詳細:
その他の方法:ループでは、ケース1の関数getUserBalance(ユーザー残高の取得)を呼び出してから、残高を加算します.
ループでは、continueを最大1回使用することを推奨します.複数回continueを使用する必要がある場合は、ループを関数にカプセル化することをお勧めします.
ケース3:条件式関数による階層の削減
次の章の「ケース2:複雑な条件式を関数にカプセル化する」を参照してください.
主な収益
パッケージング条件式関数
ケース1:単純条件式を関数にカプセル化
現象の説明:
//
public double getTicketPrice(Date currDate) {
if (Objects.nonNull(currDate) && currDate.after(DISCOUNT_BEGIN_DATE)
&& currDate.before(DISCOUNT_END_DATE)) {
return TICKET_PRICE * DISCOUNT_RATE;
}
return TICKET_PRICE;
}
推奨事項:
//
public double getTicketPrice(Date currDate) {
if (isDiscountDate(currDate)) {
return TICKET_PRICE * DISCOUNT_RATE;
}
return TICKET_PRICE;
}
//
private static boolean isDiscountDate(Date currDate) {
return Objects.nonNull(currDate) &&
currDate.after(DISCOUNT_BEGIN_DATE)
&& currDate.before(DISCOUNT_END_DATE);
}
ケース2:複雑な条件式を関数にカプセル化
現象の説明:
//
public List getRichUserList(List userList) {
//
List richUserList = new ArrayList<>();
//
for (User user : userList) {
//
UserAccount account = user.getAccount();
if (Objects.nonNull(account)) {
//
Double balance = account.getBalance();
if (Objects.nonNull(balance) && balance.compareTo(RICH_THRESHOLD) >= 0) {
//
richUserList.add(user);
}
}
}
//
return richUserList;
}
推奨事項:
//
public List getRichUserList(List userList) {
//
List richUserList = new ArrayList<>();
//
for (User user : userList) {
//
if (isRichUser(user)) {
//
richUserList.add(user);
}
}
//
return richUserList;
}
//
private boolean isRichUser(User user) {
//
UserAccount account = user.getAccount();
if (Objects.isNull(account)) {
return false;
}
//
Double balance = account.getBalance();
if (Objects.isNull(balance)) {
return false;
}
//
return balance.compareTo(RICH_THRESHOLD) >= 0;
}
以上のコードはストリーミング(Stream)プログラミングを用いたフィルタリングでも実現できる.
主な収益
不要な空ポインタ判断はできるだけ避ける
本章では,プロジェクト内部のコードにのみ適用し,自分が知っているコードであるため,不要な空ポインタ判断をできるだけ避けることができる.サードパーティのミドルウェアとシステムインタフェースについては、コードの堅牢性を保証するために、空のポインタ判断を行う必要があります.
ケース1:呼び出し関数はパラメータが空でないことを保証し、呼び出された関数は不要な空ポインタ判断をできるだけ避ける
現象の説明:
//
User user = new User();
... //
createUser(user);
//
private void createUser(User user){
//
if(Objects.isNull(user)) {
return;
}
//
userDAO.insert(user);
userRedis.save(user);
}
推奨事項:
//
User user = new User();
... //
createUser(user);
//
private void createUser(User user){
//
userDAO.insert(user);
userRedis.save(user);
}
ケース2:呼び出された関数は戻りが空でないことを保証し、呼び出し関数はできるだけ不要な空のポインタの判断を避ける。
現象の説明:
//
public void saveUser(Long id, String name) {
//
User user = buildUser(id, name);
if (Objects.isNull(user)) {
throw new BizRuntimeException(" ");
}
//
userDAO.insert(user);
userRedis.save(user);
}
//
private User buildUser(Long id, String name) {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
推奨事項:
//
public void saveUser(Long id, String name) {
//
User user = buildUser(id, name);
//
userDAO.insert(user);
userRedis.save(user);
}
//
private User buildUser(Long id, String name) {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
ケース3:付与ロジックはリストデータ項目が空でないことを保証し、処理ロジックは不要な空ポインタ判断をできるだけ避ける
現象の説明:
//
List userList = userDAO.queryAll();
if (CollectionUtils.isEmpty(userList)) {
return;
}
//
List userVoList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
UserVO userVo = new UserVO();
userVo.setId(user.getId());
userVo.setName(user.getName());
userVoList.add(userVo);
}
//
for (UserVO userVo : userVoList) {
//
if (Objects.isNull(userVo)) {
continue;
}
//
...
}
推奨事項:
//
List userList = userDAO.queryAll();
if (CollectionUtils.isEmpty(userList)) {
return;
}
//
List userVoList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
UserVO userVo = new UserVO();
userVo.setId(user.getId());
userVo.setName(user.getName());
userVoList.add(userVo);
}
//
for (UserVO userVo : userVoList) {
//
...
}
ケース4:MyBatisクエリ関数の戻りリストとデータ項目は空ではなく、空のポインタで判断できます。
MyBatisは優れた永続層フレームワークであり、プロジェクトで最も広く使用されているデータベースミドルウェアの1つです.MyBatisのソースコードを解析することで,クエリ関数が返すリストもデータ項目も空ではなく,コードでは空ポインタで判断しなくてもよい.
現象の説明:
この書き方は問題ないが,ただ保守的すぎるだけだ.
//
public List queryUser(Long id, String name) {
//
List userList = userDAO.query(id, name);
if (Objects.isNull(userList)) {
return Collections.emptyList();
}
//
List voList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
//
if (Objects.isNull(user)) {
continue;
}
//
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
voList.add(vo);
}
//
return voList;
}
推奨事項:
//
public List queryUser(Long id, String name) {
//
List userList = userDAO.query(id, name);
//
List voList = new ArrayList<>(userList.size());
for (UserDO user : userList) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
voList.add(vo);
}
//
return voList;
}
主な収益
。
転載先:https://www.cnblogs.com/gavin-guo/p/11506641.html