碼農如何提高自己的品味
作者:京東科技 文濤
前言
軟體研發工程師俗稱程式設計師經常對業界外的人自謙作碼農,一來給自己不菲的收入找個不錯的說辭(像農民伯伯那樣辛勤耕耘掙來的血汗錢),二來也是自嘲這個行業確實辛苦,辛苦得沒時間捯飭,甚至沒有駝背、脫髮加持都說不過去。不過時間久了,行外人還真就相信了程式設計師就是一幫沒品味,木訥的low貨,大部分的文藝作品中也都是這麼表現程式設計師的。可是我今天要說一下我的感受,程式設計是個藝術活,程式設計師是最聰明的一群人,我們的品味也可以像藝術家一樣。
言歸正轉,你是不是以為我今天要教你穿搭?不不不,這依然是一篇技術文章,想學穿搭女士學陳舒婷(《狂飆》中的大嫂),男士找陳舒婷那樣的女朋友就好了。筆者今天教你怎樣有“品味”的寫程式碼。
以下幾點可提升“品味”
說明:以下是筆者的經驗之談具有部分主觀性,不贊同的歡迎拍磚,要想體系化提升編碼功底建議讀《XX公司Java編碼規範》、《Effective Java》、《程式碼整潔之道》。以下幾點部分具有通用性,部分僅限於java語言,其它語言的同學繞過即可。
優雅防重
關於成體系的防重講解,筆者之後打算寫一篇文章介紹,今天只講一種優雅的方式:
如果你的業務場景滿足以下兩個條件:
1 業務介面重複呼叫的概率不是很高
2 入參有明確業務主鍵如:訂單ID,商品ID,文章ID,運單ID等
在這種場景下,非常適合樂觀防重,思路就是程式碼處理不主動做防重,只在監測到重複提交後做相應處理。
如何監測到重複提交呢?MySQL唯一索引 + org.springframework.dao.DuplicateKeyException
程式碼如下:
public int createContent(ContentOverviewEntity contentEntity) {
try{
return contentOverviewRepository.createContent(contentEntity);
}catch (DuplicateKeyException dke){
log.warn("repeat content:{}",contentEntity.toString());
}
return 0;
}
用好lambda表示式
lambda表示式已經是一個老生常談的話題了,筆者認為,初級程式設計師向中級進階的必經之路就是攻克lambda表示式,lambda表示式和麵向物件程式設計是兩個程式設計理念,《架構整潔之道》裡曾提到有三種程式設計正規化,結構化程式設計(面向過程程式設計)、面向物件程式設計、函數語言程式設計。初次接觸lambda表示式肯定特別不適應,但如果熟悉以後你將開啟一個程式設計方式的新思路。本文不講lambda,只講如下例子:
比如你想把一個二維表資料進行分組,可採用以下一行程式碼實現
List<ActionAggregation> actAggs = ....
Map<String, List<ActionAggregation>> collect =
actAggs.stream()
.collect(Collectors.groupingBy(ActionAggregation :: containWoNosStr,LinkedHashMap::new,Collectors.toList()));
用好衛語句
各個大場的JAVA程式設計規範裡基本都有這條建議,但我見過的程式碼裡,把它用好的不多,衛語句對提升程式碼的可維護性有著很大的作用,想像一下,在一個10層if 縮排的接口裡找程式碼邏輯是一件多麼痛苦的事情,有人說,哪有10層的縮排啊,別說,筆者還真的在一個微服務裡的一個核心介面看到了這種程式碼,該介面被過多的人接手導致了這樣的局面。系統接手人過多以後,程式碼腐化的速度超出你的想像。
下面舉例說明:
沒有用衛語句的程式碼,很多層縮排
if (title.equals(newTitle)){
if (...) {
if (...) {
if (...) {
}
}else{
}
}else{
}
}
使用了衛語句的程式碼,縮排很少
if (!title.equals(newTitle)) {
return xxx;
}
if (...) {
return xxx;
}else{
return yyy;
}
if (...) {
return zzz;
}
避免雙重迴圈
簡單說雙重迴圈會將程式碼邏輯的時間複雜度擴大至O(n^2)
如果有按key匹配兩個列表的場景建議使用以下方式:
1 將列表1 進行map化
2 迴圈列表2,從map中獲取值
程式碼示例如下:
List<WorkOrderChain> allPre = ...
List<WorkOrderChain> chains = ...
Map<String, WorkOrderChain> preMap = allPre.stream().collect(Collectors.toMap(WorkOrderChain::getWoNext, item -> item,(v1, v2)->v1));
chains.forEach(item->{
WorkOrderChain preWo = preMap.get(item.getWoNo());
if (preWo!=null){
item.setIsHead(1);
}else{
item.setIsHead(0);
}
});
用@see @link來設計RPC的API
程式設計師們還經常自嘲的幾個詞有:API工程師,中介軟體裝配工等,既然咱平時寫API寫的比較多,那種就把它寫到極致@see @link的作用是讓使用方可以方便的連結到列舉型別的物件上,方便閱讀
示例如下:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContentProcessDto implements Serializable {
/**
* 內容ID
*/
private String contentId;
/**
* @see com.jd.jr.community.common.enums.ContentTypeEnum
*/
private Integer contentType;
/**
* @see com.jd.jr.community.common.enums.ContentQualityGradeEnum
*/
private Integer qualityGrade;
}
日誌列印避免只打整個引數
研發經常為了省事,直接將入參這樣列印
log.info("operateRelationParam:{}", JSONObject.toJSONString(request));
該日誌進了日誌系統後,研發在搜尋日誌的時候,很難根據業務主鍵排查問題
如果改進成以下方式,便可方便的進行日誌搜尋
log.info("operateRelationParam,id:{},req:{}", request.getId(),JSONObject.toJSONString(request));
如上:只需要全詞匹配“operateRelationParam,id:111”,即可找到業務主鍵111的業務日誌。
用異常捕獲替代方法引數傳遞
我們經常面對的一種情況是:從子方法中獲取返回的值來標識程式接下來的走向,這種方式筆者認為不夠優雅。
舉例:以下程式碼paramCheck和deleteContent方法,返回了這兩個方法的執行結果,呼叫方通過返回結果判斷程式走向
public RpcResult<String> deleteContent(ContentOptDto contentOptDto) {
log.info("deleteContentParam:{}", contentOptDto.toString());
try{
RpcResult<?> paramCheckRet = this.paramCheck(contentOptDto);
if (paramCheckRet.isSgmFail()){
return RpcResult.getSgmFail("非法引數:"+paramCheckRet.getMsg());
}
ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class);
RpcResult<?> delRet = contentEventHandleAbility.deleteContent(contentEntity);
if (delRet.isSgmFail()){
return RpcResult.getSgmFail("業務處理異常:"+delRet.getMsg());
}
}catch (Exception e){
log.error("deleteContent exception:",e);
return RpcResult.getSgmFail("內部處理錯誤");
}
return RpcResult.getSgmSuccess();
}
我們可以通過自定義異常的方式解決:子方法丟擲不同的異常,呼叫方catch不同異常以便進行不同邏輯的處理,這樣呼叫方特別清爽,不必做返回結果判斷
程式碼示例如下:
public RpcResult<String> deleteContent(ContentOptDto contentOptDto) {
log.info("deleteContentParam:{}", contentOptDto.toString());
try{
this.paramCheck(contentOptDto);
ContentOverviewEntity contentEntity = DozerMapperUtil.map(contentOptDto,ContentOverviewEntity.class);
contentEventHandleAbility.deleteContent(contentEntity);
}catch(IllegalStateException pe){
log.error("deleteContentParam error:"+pe.getMessage(),pe);
return RpcResult.getSgmFail("非法引數:"+pe.getMessage());
}catch(BusinessException be){
log.error("deleteContentBusiness error:"+be.getMessage(),be);
return RpcResult.getSgmFail("業務處理異常:"+be.getMessage());
}catch (Exception e){
log.error("deleteContent exception:",e);
return RpcResult.getSgmFail("內部處理錯誤");
}
return RpcResult.getSgmSuccess();
}
自定義SpringBoot的Banner
別再讓你的Spring Boot啟動banner千篇一律,spring 支援自定義banner,該技能對業務功能實現沒任何卵用,但會給枯燥的程式設計生活新增一點樂趣。
以下是官方文件的說明: http://docs.spring.io/spring-boot/docs/1.3.8.RELEASE/reference/htmlsingle/#boot-features-banner
另外你還需要ASCII藝術字生成工具: http://tools.kalvinbg.cn/txt/ascii
效果如下:
_ _ _ _ _
(_|_)_ __ __ _ __| | ___ _ __ __ _ | |__ ___ ___ | |_ ___
| | | '_ \ / _` | / _` |/ _ \| '_ \ / _` | | '_ \ / _ \ / _ \| __/ __|
| | | | | | (_| | | (_| | (_) | | | | (_| | | |_) | (_) | (_) | |_\__ \
_/ |_|_| |_|\__, | \__,_|\___/|_| |_|\__, | |_.__/ \___/ \___/ \__|___/
|__/ |___/ |___/
多用Java語法糖
程式語言中java的語法是相對繁瑣的,用過golang的或scala的人感覺特別明顯。java提供了10多種語法糖,寫程式碼常使用語法糖,給人一種 “這哥們java用得通透” 的感覺。
舉例:try-with-resource語法,當一個外部資源的控制代碼物件實現了AutoCloseable介面,JDK7中便可以利用try-with-resource語法更優雅的關閉資源,消除板式程式碼。
try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
System.out.println(inputStream.read());
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
利用鏈式程式設計
鏈式程式設計,也叫級聯式程式設計,呼叫物件的函式時返回一個this物件指向物件本身,達到鏈式效果,可以級聯呼叫。鏈式程式設計的優點是:程式設計性強、可讀性強、程式碼簡潔。
舉例:假如覺得官方提供的容器不夠方便,可以自定義,程式碼如下,但更建議使用開源的經過驗證的類庫如guava包中的工具類
/**
鏈式map
*/
public class ChainMap<K,V> {
private Map<K,V> innerMap = new HashMap<>();
public V get(K key) {
return innerMap.get(key);
}
public ChainMap<K,V> chainPut(K key, V value) {
innerMap.put(key, value);
return this;
}
public static void main(String[] args) {
ChainMap<String,Object> chainMap = new ChainMap<>();
chainMap.chainPut("a","1")
.chainPut("b","2")
.chainPut("c","3");
}
}
未完,待續,歡迎評論區補充
- 應用健康度隱患刨析解決系列之資料庫時區設定
- 對於Vue3和Ts的心得和思考
- 一文詳解擴散模型:DDPM
- zookeeper的Leader選舉原始碼解析
- 一文帶你搞懂如何優化慢SQL
- 京東金融Android瘦身探索與實踐
- 微前端框架single-spa子應用載入解析
- cookie時效無限延長方案
- 聊聊前端效能指標那些事兒
- Spring竟然可以建立“重複”名稱的bean?—一次專案中存在多個bean名稱重複問題的排查
- 京東金融Android瘦身探索與實踐
- Spring原始碼核心剖析
- 深入淺出RPC服務 | 不同層的網路協議
- 安全測試之探索windows遊戲掃雷
- 關於資料庫分庫分表的一點想法
- 對於Vue3和Ts的心得和思考
- Bitmap、RoaringBitmap原理分析
- 京東小程式CI工具實踐
- 測試用例設計指南
- 當你對 redis 說你中意的女孩是 Mia