Duke的咆哮語錄①:我求求你們讀一下《代碼整潔之道》吧!

命名是一門大學問。

最近接手一個別人開發的項目,讓人無限黑人問號。其實類似行文風格的文章我早就想做了,但這一次應該說是最最強烈的。一個專業的程序員應該是負責任的、有遠見的、善良的。應該嫉惡如仇,極度厭惡任何非秩序行爲代碼的。本着對同事進步的督促、對不爽導致健康問題的預防和發泄,現開啓本系列文章,名曰咆哮語錄,希望能以吾輩血淚之咆哮,咄咄逼人之氣勢,給同事同學留下絲毫之印象。
以下皆爲工作時隨手抓取的代碼片段。經目測沒有包含任何公司機密,也不會涉及知識產權敏感。但依然不允許任何人以任何形式用於任何項目。
本文最大的功能還是自己的不吐不快希望自己冷靜,我並不是也沒有權利批評任何人。請不要被我的咆哮影響心態,謝謝。
我們先來看Controller層的代碼:

// 省略代碼頭部

@Controller
@RequestMapping("/file")
public class FileController {

    @Autowired <1>
    TrainPlanService planService; <2>

    /*上傳*/
    @ResponseBody
    @RequestMapping(value = "/upload")
    public SimpleJSON upload(HttpServletRequest request,
                             MultipartFile file, TrainPlan plan) throws Exception { <3>

        try {
            Boolean flag = ReflectUtils.checkNotNull(plan);
            if (!flag) {
                return ResultUtils.error201();
            }
            if (file.isEmpty()) {
                return ResultUtils.customResult(500, "文件不能爲空,請重新上傳!");
            }
            //判斷是否存在若存在刪除該文件
            planService.delete(plan);

            String savedDir = ReadeGlobePa.getValueByProper("fileSaveDir");
            //上傳文件路徑
//            String savedDir = request.getSession().getServletContext().getRealPath(dir); //獲取服務器指定文件存取路徑 <4>
            //上傳文件名
            String filename = file.getOriginalFilename();
            int pointIndex = filename.indexOf(".");      //點號的位置 <5>
            String fileSuffix = filename.substring(pointIndex);             //截取文件後綴 <5>
            UUID fileId = UUID.randomUUID();                        //生成文件的前綴包含連字符 <5>
            String savedFileName = fileId.toString().replace("-", "").concat(fileSuffix);       //文件存取名
            File savedFile = new File(savedDir, savedFileName);
            //判斷路徑是否存在,如果不存在就創建一個
            if (!savedFile.getParentFile().exists()) {
                savedFile.getParentFile().mkdirs();
            }
            //將上傳文件保存到一個目標文件當中
            file.transferTo(savedFile);

            //保存數據庫表
            //plan.setUrl(savedDir + savedFileName);
            plan.setUrl(savedFileName);
            plan.setFileName(filename);
            planService.save(plan);
            return ResultUtils.customResult(200, "上傳成功!");
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*跳轉上傳頁面*/
    @GetMapping("/toUploadPage")
    public String toUploadPage() throws Exception { <3>
        return "/file";
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findBranchList")
    public SimpleJSON findBranchList() throws Exception { <3>
        try {
            List<String> branchList = planService.findBranchList();
            return ResultUtils.getSuccess(branchList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findPlanList")
    public SimpleJSON findPlanList(@RequestParam Integer year, Integer quarter, @RequestParam String branchName) throws Exception {
        try {
            String branch = URLDecoder.decode(branchName, "UTF-8");
            List<TrainPlan> planList = planService.findPlanList(year, quarter, branch);
            return ResultUtils.getSuccess(planList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }

    /*分中心列表*/ <6>
    @ResponseBody
    @GetMapping("/findList")
    public SimpleJSON findList(@RequestParam Integer year, Integer quarter) throws Exception {
        try {

            List<TrainPlan> planList = planService.findList(year, quarter);
            return ResultUtils.getSuccess(planList);
        } catch (Exception e) {
            return ResultUtils.error500(e.getClass().getName());
        }
    }
}

先看標號的部分。

  1. @Autowired註解用於字段注入本身語法上沒錯,但field注入(字段注入)在目前的Java開發中是明顯不受待見的。它存在諸如“容易違反單一原則”等諸多問題。可參閱這篇文章
  2. 縱然,使用字段注入就使用字段注入罷。但該成員變量很明顯絕對不可能和外接發生接觸,故可訪問性應該爲私有,應加private
    其實說來,關於Spring的注入,現在主流且推薦的方式就是setter注入和構造器注入。兩種方法都很好,但我個人更推薦使用構造器注入。本例使用構造器注入改寫的樣子如下:
    @Autowired
    public FileController(TrainPlanService planService) {
        this.planService = planService;
    }
    private final TrainPlanService planService;

如果是setter注入的話,則應改寫如下:

    private TrainPlanService planService;
    @Autowired
    public void setPlanService(TrainPlanService planService) {
        this.planService = planService;
    }

注意構造器注入的例子我們將planService聲明爲了final的,這也是使用構造器注入的一點附加好處,可以避免運行時對注入字段進行修改導致一些問題。

  1. 稍微有些Spring Web開發經驗的人應該都知道,Controller層應該是一切異常的終點。如果在Controller層拋出異常,那麼該異常就會被Spring或容器捕獲,如果沒有任何配置的話,就會顯示出一張極醜無比的白標頁。甚至還會暴露生產環境信息,被黑客利用。所以我多次強調:絕對不允許在Controller層拋出任何異常,也絕對不要在Controller層的方法上加入任何異常拋出聲明。可惜,這段代碼顯然沒有做到後一點。醜陋而又無用的throws聲明,讓人哭笑不得。關於異常的問題我會在下文詳述。
  2. 第四點,參差不齊的行註釋開始行位置。呵呵,總有一天我會被你們這羣沒有強迫症的人逼上絕路。您按一兩下tab鍵對齊一下行嗎?求求您了!還有就是很明顯這行代碼是遺棄代碼,永遠不可能再發揮作用了。求求您刪了它吧!我很不明白,有些人,明明有些代碼再也不會用到,非要當寶貝一樣加個註釋留在代碼裏隨時光慢慢變老。抱歉那些不是寶貝,是屎啊!是尼瑪惡臭不堪又毫無用處的屎啊!是毫無幫助又會誤導人的屎啊!關於註釋,下文我還會談到。
  3. 好了,這次不用單行註釋,改用行末註釋了。我個人堅決抵制任何超過5個字的行末註釋。它們不僅嚴重拖長了代碼行寬,也在事實上不會比獨行註釋易讀。OK即使我們拋開這一點不談,我求求您了,您能不能讓所有行末註釋距離代碼語句末尾的空格數一致?這既不對齊又隨性的空格數,是蘊藏着什麼常人難以發現的美麗嗎?我建議,如果真的要使用行末註釋,您也不用刻意保持對齊,就在代碼行末後點一個空格,寫兩個/,再點一個空格開始寫就可以了。換種說法麻煩//前後各加一個空格謝謝。您也方便,我們也舒服。先忽略這段代碼中的邏輯問題,比如如果上傳文件名本身沒有.怎麼辦,以及刪除沒必要是個人都能看得懂的代碼上的註釋,以及適當添加空行,讓我來改寫一下:
    // 上傳文件名
    String filename = file.getOriginalFilename();
    int pointIndex = filename.indexOf(".");
    // 文件後綴
    String fileSuffix = filename.substring(pointIndex);
    // 生成“UUID.後綴名”文件名
    String savedFileName = UUID.randomUUID().toString().replace("-", "").concat(fileSuffix);
    
    File savedFile = new File(savedDir, savedFileName);
    // 判斷路徑是否存在,如果不存在就創建一個
    if (!savedFile.getParentFile().exists()) {
        savedFile.getParentFile().mkdirs();
    }
    // 將上傳文件保存到一個目標文件當中
    file.transferTo(savedFile);

如果您覺得我這樣寫不如原文舒服,那您可以直接點一下xx江湖再見咯。

  1. 我們來看最後一個標記,這裏原作者很貼心地加了註釋,告訴你該方法的功能是“分中心列表”。但實際上呢?只有第一個方法確實是用來返回分中心列表的。後兩個要獲取的東西壓根不是分中心。這就是有害的註釋。一些剛入坑一兩年的小白白在評價項目的時候,最喜歡的說辭就是“這項目太垃圾了,連註釋都沒有,根本看不懂”。還有很多小白說勵志給自己寫的每一行代碼都加上註釋。其實都是片面的。註釋要加,但不要加廢話。註釋不能不加,但如果是有害的有誤導的註釋,那我求求您不如不加。良好的軟件項目代碼是自文檔化的,還真不見得需要添加太多註釋。而有害的註釋,則是軟件項目的毒瘤,比屎還危險的東西,是堅決要燒死的。

我們再來看最後兩個方法的命名,當然註釋不指望了,毫無幫助。那麼這兩個方法的名字有沒有問題呢?

public SimpleJSON findPlanList(@RequestParam Integer year, Integer quarter, @RequestParam String branchName) throws Exception
public SimpleJSON findList(@RequestParam Integer year, Integer quarter) throws Exception

看代碼可以知道,這兩個方法都返回“TrainPlan”的列表。也就是說兩者的名字只是有無冗餘的Plan而已,實質則等價。這是其一。其二,list本身即爲動詞“列舉”,那何必再使用“find”動詞呢?直接叫list豈不是更簡單直接?
還有,兩個方法的表面很像,返回值很像,但很明顯調用的service方法不同,這也就意味着兩者在邏輯上的含義是不同的。如此,也就意味着至少應該用明確的定語“ForXXX”、“ByXXX”、“WithXXX”等去描述該方法,可是並沒有。後來瞭解到,實際上這兩個方法調用的service方法功能上又是基本等價的,完全可以刪掉其中一個。呵呵。
從命名的角度來說,該控制器名爲“FileController”,那麼從字面的意思理解,這段代碼就應該是處理文件的。但事實上我們看其內容,發現大部分業務邏輯卻是派發給TrainPlanService的實例去完成的。也就是說,從事實上來看,該控制器的命名本身就是非常糟糕的。應該直接命名爲TrainPlanController,其映射的二級目錄也應該爲/trainPlan纔對。
那麼既然該控制器主要用於處理培訓計劃,那麼主業務邏輯bean,planService,添加所謂的前綴就顯得贅餘了。不如直接改爲service更加爽利。
好了,吐槽這麼多,再來教一招,IDEA的重構功能,可以很簡單地給標識符、局部變量重命名,也可以很簡單地抽取大代碼片段爲私有方法。我們這裏介紹重命名的做法。

  1. 光標移動至要改名的類名/變量名/方法名上;
  2. 按下Shift + F6
  3. 輸入新的名字,回車。

您學會了嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章