要用一種精緻的態度去寫代碼,才能寫出優美而牢固的代碼。java
本文主要從平常代碼中摘錄一些不良的寫法。這些不良的寫法會擾亂清晰的主流程,淹沒重要的業務邏輯,使得代碼語義難以理解和修改。
app
超長鏈式的壞處: 1. getXXX() 重複出現; 2. 容易 NPE ;3. 很是醜函數
if (response.getData() != null && CollectionUtils.isNotEmpty(response.getData().getShoppingCartDTOList())) { // 出參轉換 cartList = response.getData().getShoppingCartDTOList().stream().map(CartResponseBuilderV2::buildCartList).collect(Collectors.toList()); }
寫成下面較好:工具
像 StreamUtil 這樣的工具類應該抽離出來,方便其餘同窗更好地寫代碼。ui
T data = response.getData(); if (data != null && CollectionUtils.isNotEmpty(data.getShoppingCartDTOList())) { cartList = StreamUtil.map(data.getShoppingCartDTOList(), CartResponseBuilderV2::buildCartList); }
一個很大的入口方法 A ,調用了 B 和 C , 只對 A 作了單測,認爲 B 和 C 天然覆蓋。this
壞處:1. 當 B 和 C 添加新的代碼路徑時,要寫單測很是困難; 2. A 的單測代碼不必定覆蓋 B,C 的全部重要狀況。code
只要 B 和 C 含有比較重要的業務點,尤爲是比較複雜的業務邏輯時,就應該加以單測。ip
很容易就順手寫出這樣的代碼: 把全部一坨東西扔到一個語句,就像把全部一坨代碼扔到一個方法裏同樣。ci
long discounts = Optional.ofNullable(orderItemInfo).map(OrderItemInfo::getPrice).orElse(0L) - Optional.ofNullable(orderItemInfo).map(OrderItemInfo::getPcOrderItemPrice) .map(PcOrderItemPrice::getUnitPrice).orElse(0L);
應該把變量抽離處理:get
Long originPrice = Optional.ofNullable(orderItemInfo).map(OrderItemInfo::getPrice).orElse(0L); Long unitPrice = Optional.ofNullable(orderItemInfo).map(OrderItemInfo::getPcOrderItemPrice) .map(PcOrderItemPrice::getUnitPrice).orElse(0L); long discounts = originPrice - unitPrice
這種也是暗藏危機:
BatchQuerySnapshotDTO requestParam = buildRequestParam(Long.parseLong(orderInfoList.get(0).getShopId()), sameUserVersionOrderMap.keySet(), exportContext);
這裏取 shopId 用了 Long.parseLong(orderInfoList.get(0).getShopId()) 並且還寫在方法調用參數列表裏,這樣有兩個壞處:
看下面這段代碼:
private List<Get> buildGets(List<String> rowKeyList, String cf, List<String> columns, List<String> columnPrefixFilters) { return StreamUtil.map( rowKeyList, rowKey -> { String rowKeyNotEmpty = (rowKey == null ? "null" : rowKey); Get get = new Get(Bytes.toBytes(rowKeyNotEmpty)); if (columns != null && !columns.isEmpty()) { for (String col: columns) { get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(col)); } } MultipleColumnPrefixFilter multipleColumnPrefixFilter = buildMultipleColumnPrefixFilter(columnPrefixFilters); if (multipleColumnPrefixFilter != null) { get.setFilter(multipleColumnPrefixFilter); } return get; }); }
其中 rowKey -> { // code... } 中的 塊 code 是沒有抽離出來的,這樣寫的話,很容易就忽視對裏面代碼的單測,從而埋伏潛在的BUG。應該將這樣的塊 code 抽離成一個函數,並加以單測。
private List<Get> buildGetsV2(List<String> rowKeyList, String cf, List<String> columns, List<String> columnPrefixFilters) { return StreamUtil.map(rowKeyList, rowKey -> buildGet(rowKey, cf, columns, columnPrefixFilters) ); } private Get buildGet(String rowKey, String cf, List<String> columns, List<String> columnPrefixFilters) { String rowKeyNotEmpty = (rowKey == null ? "null" : rowKey); Get get = new Get(Bytes.toBytes(rowKeyNotEmpty)); if (columns != null && !columns.isEmpty()) { for (String col: columns) { get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(col)); } } MultipleColumnPrefixFilter multipleColumnPrefixFilter = buildMultipleColumnPrefixFilter(columnPrefixFilters); if (multipleColumnPrefixFilter != null) { get.setFilter(multipleColumnPrefixFilter); } return get; }
這段代碼並無問題,可是不夠優美。
public Boolean kdtIdsLimit(LimitStageEnum limitStageEnum, Set<Long> kdtIds) { for (Long kdtId : kdtIds) { Map<String, String> features = Maps.newHashMap(); features.put("stage", limitStageEnum.getValue()); features.put("kdt_id", kdtId.toString()); TrafficLimitDecision decision = programmingTrafficLimitAdapter.apply(features); if (decision.shouldLimit()) { return true; } } return false; }
優美的寫法是什麼 ? 該分離的關注點分離出來,努力尋求最簡潔的方式表達。
這段代碼含有兩個語義:1. 對一個 shopId 判斷是否限流; 2. 若是有一個限流,則全部都要限流。一個是單店鋪的限流判斷的實現,一個是多個店鋪的限流匹配規則。應該分離開。
public Boolean shopIdsLimit(LimitStageEnum limitStageEnum, Set<Long> shopIds) { return shopIds.stream().anyMatch(this::shouldLimit); } private boolean shouldLimit(Long shopId) { Map<String, String> features = Maps.newHashMap(); features.put("stage", limitStageEnum.getValue()); features.put("shop_id", shopId.toString()); TrafficLimitDecision decision = programmingTrafficLimitAdapter.apply(features); return decision.shouldLimit(); }
看下面這段代碼,三個 map 的邏輯很複雜,放在裏面:
壞處:1. 複雜邏輯沒法單測,很容易出 BUG ,很容易 NPE 或 各類異常; 2. 流程邏輯不清晰。
你能看懂這裏面寫的是什麼嗎 ?
private Map<Pair<Long, Integer>, List<String>> aggregateSameUserVersionOrder( List<OrderInfo> orderInfoList) { Map<Pair<Long, Integer>, List<String>> sameVersionOrderMap = Maps.newHashMap(); orderInfoList.forEach(orderInfo -> Optional.ofNullable(orderInfo.getTcExtra()) .map(extra -> extra.get(EDU_STUDENT_INFO)) // extraMap中存的是String .map(studentInfoStr -> JsonUtil.readMap((String) studentInfoStr)).map(studentInfo -> { Long studentId = (Long) studentInfo.get(STUDENT_ID); Integer version = (Integer) studentInfo.get(VERSION); if (studentInfo.containsKey(ID_CARD_NO) && studentId != null && version != null) { return Pair.of(studentId, version); } else { return null; } }).ifPresent( idVersionPair -> sameVersionOrderMap .computeIfAbsent(idVersionPair, k -> Lists.newArrayList()) .add(orderInfo.getOrderNo()))); return sameVersionOrderMap; }
這樣繞口的代碼,最好用比較平易的方式表達更佳。 不要濫用語言特性。
private Map<Pair<Long, Integer>, List<String>> aggregateSameUserVersionOrder(List<OrderInfo> orderInfoList) { Map<Pair<Long, Integer>, List<String>> sameUserVersionOrder = new HashMap<>(); for (OrderInfo orderInfo: orderInfoList) { Pair<Long, Integer> p = generatePair(orderInfo.getTcExtra()); if (p == null) { continue; } List<String> orderNoList = sameUserVersionOrder.put(p, sameUserVersionOrder.getOrDefault(p, new ArrayList<>()); orderNoList.add(orderInfo.getOrderNo()); } return sameUserVersionOrder; } private Pair<Long, Integer> generatePair(Map<String,Object> tcExtra) { if (tcExtra == null || !tcExtra.containsKey(EDU_STUDENT_INFO)) { return null; } Map studentInfoMap = JsonUtil.readMap(tcExtra.get(EDU_STUDENT_INFO).toString()); Long studentId = (Long) studentInfoMap.get(STUDENT_ID); Integer version = (Integer) studentInfoMap.get(VERSION); if (studentInfoMap.containsKey(ID_CARD_NO) && studentId != null && version != null) { return Pair.of(studentId, version); } else { return null; } }
代碼是邏輯與表達的藝術。
代碼編寫,就像一門手工藝。要打造出優秀的做品,良好的習慣和細節是關鍵。改掉不良的代碼習慣,堅持清爽整潔之道,才能讓代碼更優美而牢固。
什麼是好的代碼習慣 ? 兩個小原則:
拆解。凡是有比較複雜的邏輯或關注點,分離細小可複用的關注點, 分離出來做爲變量或函數。
簡潔。始終追求簡潔易懂的表達。