# clean code https://www.youtube.com/watch?v=g2nMKzhkvxw https://www.youtube.com/watch?v=5B587bQ-TNg https://github.com/academind/clean-code-course-code/tree/obj-extra-attachments pdf https://garrett-massey.medium.com/5-tips-to-simplify-laravel-code-471d0c28ceed https://ithelp.ithome.com.tw/articles/10266462 php https://learnku.com/docs/clean-code-php/function/12661 ## Use Laravel’s exists() instead of count() to verify a model instance exists ![](https://i.imgur.com/Yp4uBom.png) ## Simplify if/else statements ![](https://i.imgur.com/xxpm8LA.png) ## laravel 12技巧 https://christoph-rumpel.com/2020/8/refactoring-php ## aleary return 不要波動拳 ![](https://i.imgur.com/YWwt2mo.png) 這種才要 ![](https://i.imgur.com/GmAOA9c.png) ## 傳太多參數進去class 可以改用function 在returen this會來 ![](https://i.imgur.com/I2KGIEj.png) 變成 ![](https://i.imgur.com/P9byzSe.png) 詳細可以看api那邊的fluent api ex ->fill()->save() 這邊creatr就有用到這種概念 ## 避免副作用 如果函数取一个值并返回另外一个值或多个值之外,就会产生副作用。副作用可能是写入一个文件,修改一些全局变量,或者不小心将您的所有钱汇给一个陌生人。 现在,你确实需要在程序中偶尔出现副作用。就像之前的例子一样,你可能需要写入文件。你想要做的是集中执行此操作的位置。没有几个可以写入特定内容的函数和类文件。只有一个服务可以做到这一点。 也只有一个。 主要的一点是要避免常见的陷阱,比如在对象之间共享状态而不使用任何结构,使用任何可以被写入的数据类型,而不是集中在副作用发生的地方。如果你能做到这点,你将会更快乐,比绝大多数程序员都要好。 ![](https://i.imgur.com/4Yar6W8.png) ![](https://i.imgur.com/95nmNZp.png) ## 解決多重swith https://www.youtube.com/watch?v=G-3Cdesbr44 key value php那邊有寫道 注意這不能用到function 因為array建立時會先跑 你可以看php那邊我踩過雷 如果要function呢 ![](https://i.imgur.com/nGMW38Z.png) 看看能不能組出來 不能就沒辦法了 ![](https://i.imgur.com/NkYHRby.png) ## 同型態 不要一下string 一下int 不要 ![](https://i.imgur.com/eDNB9sO.png) 這樣 ![](https://i.imgur.com/67QBuLA.png) ## 小心null 跟 undefind 解決方法if判斷 但會嵌套 所以要if ( == null) return 這樣配合提早return ## 命名完整 包含function 名稱 跟 const名稱 ## function 預設 ![](https://i.imgur.com/DVYHLih.png) 如果是array 或object給他預設 怕有些地方抱錯 有幾個參數 裡面用到的 就都要考慮沒有的情況 跟null還有nudefind(給預設)的情況 第一個要防範問題 null 跟undefind ## 不要同一個變數用到結束 這我真的覺得很特別 高級到進階 這高階 ![](https://i.imgur.com/04Ex8Ld.png) 專家 ![](https://i.imgur.com/AgfxN0T.png) 把每個都分開 然後return組起來 而不是一直追蹤他的變化 一行就單一的概念 ## function名稱 就對應裡面的功能 多的就分解開來 ## 課程開始 ### name 所有name都要有意義 包含所謂的class 跟 function 類別 (Class) 與方法 (Method) 的命名 * 類別和物件宜使用名詞來命名 * 例如: AddressParser、Customer * 避免 Data、Info、Manager、Processor 這類含糊的詞彙 * 不要使用動詞 * 方法應該使用動詞來命名,且: * 取出器 (accessors): 使用 get 當字首 * 修改器 (mutators): 使用 set 當字首 * 判定器 (predicates) 使用 is 當字首 ### 每個概念都只使用一個詞 * 不同類別的存取方法,與其分別取為 "fetch"、"retrieve"、"access",不如統一取作 "get" * DeviceManager (裝置管理者) 和 ProtocolController (協定控制器)、Driver (驅動程式) 之間的實質差別是什麼呢? 是否能統一使用 Controller 或 Manager? 「替單一抽象概念挑選一個字詞,並堅持持續地使用它」 ### 排版 空白間隔和密度可用來表示關聯強弱度 ``` /* Eaxmple 1 */ (-b+Math.sqrt(determinant))/(2*a); // vs. (-b + Math.sqrt(determinant)) / (2*a); /* Eaxmple 2 */ return b*b-4*a*c; // vs. return b*b - 4*a*c; ``` ### 變數要描述內容 使用名詞或短語與形容詞 ex userDate isVaild 變數 物件跟string要描敘內容 boolean特別一點 要用 is去寫 這些是基本 在這之上更詳細的描敘 是甚麼東西 user 是哪種user name是第一還是最後的之類的 ![](https://i.imgur.com/Ikt89HO.png) 如何才是好的 ![](https://i.imgur.com/lll38gi.png) 你看 userDate其實是不好的 原因在於不夠具體 直接命名user就好 或直接說他是customer更具體 ### method 跟 function 使用動詞或短語與形容詞 ex sentDate() inputIsVaild() 更詳細的描敘用到的東西 boolean是替外 當你結果是boolean你要用 is之類的去寫 當然要寫明 ![](https://i.imgur.com/Q07pn8Q.png) ![](https://i.imgur.com/vHV00H6.png) ### class 使用名詞或帶名詞的短語 user requestBody ![](https://i.imgur.com/FTwGYAW.png) 不要過度命名 ## 例外跟小心 原生的就不要管了 例外的比較偏向 get set魔術方法 (js) 那些就不用再加上get開頭之類的 因為他本身就算是get方法 這種比較偏向資源 ### 命名不要太具體 ![](https://i.imgur.com/8Gt7V0V.png) ### 不要用釐語 跟 縮寫 還有 虛假信息 ex userList:{ u1:x, u2:x} 這是錯的 他不是清單 他是object 還有 allUser = use.filter 雖然要表達過濾剩下的是要的全部 但別人看不懂 而且這樣不代表all全部的user 最好叫做filterUser 當然這不是最好 因該看你過濾的 剩下的是甚麼 還沒付款嗎? 之類的 ![](https://i.imgur.com/b9aerTF.png) ### 不要全部都用data 不夠詳細 盡量不要 ![](https://i.imgur.com/rZQnwXC.png) ### 同一種寫法 不要跳來跳去 ex 你用 get fetch retrive 之後都這樣用 ![](https://i.imgur.com/yMJ5oRh.png) ## 改變放置地方 一開始寫法 ![](https://i.imgur.com/RZI4I9O.png) 改成放在裡面 ![](https://i.imgur.com/AzstNTy.png) 這樣的好處是在 一開始調用使用 blog_post . print_blog_post 改好變成 blog_post . print 這樣就不用重複寫 因為他在他class裡面 先調用blogPost 所以你要印出放在這class裡面就不用再加prefix了 ## 註解 平常是不要寫 會造成多餘的修改 應該用好的名稱替代 例外是可能這api有特定的環境 或正規表達式這種 比較麻煩的 ![](https://i.imgur.com/fTDM1ZH.png) 還有TODO   ## 代碼格式 分為垂直跟水平 垂直是指上到下 不應該跳來跳去 好的code的一篇文章 你不該第一頁跳去第三頁這樣吧 function之間要有空白 才好閱讀 相關的事情不要空白 相關的function放上下 相近位置 另外 ## function 「關於函式的首要準則,就是要簡短。第二項準則,就是要比第一項的簡短函式還要更簡短。這是一個我無法證明的主張」 「我曾經寫過令人難受的 3000 行函式怪物,寫過數不清的 100 至 300 行大小的函式,也寫過只有 20 到 30 行的函式。這些經驗告訴我,函式應該要非常簡短」 取自: Clean Code (p.40) 最好不要三個(兩個很好看順序 三個就開始難了) 三個以上要避免 這樣太亂 ![](https://i.imgur.com/Z0UgIaM.png) 如果真的要三個呢 解決方法 ex 1 ![](https://i.imgur.com/NZsHHbP.png) 你把你要做的放在外面 這樣就不會很亂了 也只要一個參數 不然原本還要看你要做啥 ex 2 ![](https://i.imgur.com/PWLXXKt.png) 用城class這是裡面的function 更推薦用這個 **一個參數** 但如果今天是一個參數的function就不用特別開class了 因為已經夠好理解了 這樣有點過度設計 **兩個參數** 會有先後順序問題 最好要直觀 eamil 跟密碼 這是登入的 一般eamil都是先的 如果可以拆除 最好分開 ![](https://i.imgur.com/32lN3s2.png) 變成 ![](https://i.imgur.com/qEMIwTv.png) 兩個以上呢 ![](https://i.imgur.com/OlKO3yh.png) 這很重要 使用class這樣你只要傳物件進去 不用管他順序 如果你用function等於要限定順序 範例二 ![](https://i.imgur.com/OH5oWB9.png) 變成 ![](https://i.imgur.com/EYgiJTi.png) 但如果今天是動態的 可能會邊多變少 就像excel之前寫得那樣的一樣 每個語言一定都有相關的展開運算子 ![](https://i.imgur.com/qfVs9yC.png) ### 免輸出型參數 (Output Parameter) 閱讀函式時,我們習慣「參數 輸入 (Input) 到函式」的概念,而 輸出(Outpu) 則是透過 Return 來回傳。我們並不會預期回傳的資訊是透過參數來傳遞 故 `StringBuffer transform(StringBuffer in) 會比 void transform(StringBuffer out)` 更洽當 關於何時可以使用輸出型參數,StackOverflow 上有許多的探討 [5],絕大多數情況我們都該避免 Output Parameter。目前筆者覺得最適用的情境在於資料庫 Stored Procedure [4] 的撰寫 不要使用旗標參數 (Flag Parameter) ### 「使用旗標參數是一種非常爛的做法」 與其將 boolean 值傳遞給函式,不如直接 Return 處理完後的 boolean 值。或者直接拆成不同函式 例子: ``` render(true) render(false) // vs. renderForSuite() renderForSingleTest() ``` ## 輸出 ![](https://i.imgur.com/pOdOGpI.png) 如果你要寫function 因為你有修改所以最好要叫add之類的 create太籠統 他會以為創建新的 當然可以的話最好用class ## function 小 單一原則 雖然是簡單的if else 但最好也抽 這樣會比較好理解 ![](https://i.imgur.com/u2OtFZG.png) 就算是小的也要寫成function 因為這樣好閱讀 ![](https://i.imgur.com/7XYXbYN.png) 這樣差距就很明顯了 概念是 一個高級操作對應一個低級抽象操作 高級操作可能是驗證是不是email 對應的低級抽象概念是 js內置的include檢查 因為include知道是檢查裡面 但他不知道為什麼要檢查 所以用function 給他賦予名稱意思 ![](https://i.imgur.com/MtqqNzR.png) 實際用到 原本 ![](https://i.imgur.com/vXfFjqu.png) 改良 ![](https://i.imgur.com/8agjnD4.png) ### 如何判斷要不要拆分 ![](https://i.imgur.com/4D2N93D.png) 第一條 可以把相關的function統一 這樣就不會寫一堆 第二條 如果你要註解 ex畫面那個 你要寫說判斷是不是eamil 或會讓人不直觀的 就寫成function ### 實戰教學 同樣層級的抽象 抽出來 都是檢查驗證 所以雖然都是不同驗證規則 但都是用來驗證input 所以他們是同樣抽象 **是根據概念去改善成function 而不是動作** 原本 ![](https://i.imgur.com/aOhVjl7.png) 改善 ![](https://i.imgur.com/j3arNp1.png) 他把同樣驗證抽出來 驗證分為檢查規則 跟丟錯誤 為什麼他改用error不用log 因為他說這不符合這function內容 因該像下面一樣 try catch去用 驗證function不拆顯示錯誤 單一原則記住 最後你執行時 ![](https://i.imgur.com/38uJ4vP.png) 這樣比較好 可以重複利用 ### 重複提取 不用太細 可以等到要用到在特別把它用細 記住要好讀再拆 不然不用做 ## 控制結構 https://ithelp.ithome.com.tw/articles/10268084 物件將它們的資料隱藏在抽象層後方,然後將操縱這些資料的函式暴露在外。資料結構則將資料暴露在外,且未提供有意義的函式」 「它們不僅是對立的,且本質上也是互補的」 第一 用工廠涵式根多態性 舉例 如果你今天有三種付費方式 都是 A.GET 使用工廠模式 第二 使用正面講述 EX isEmpty vs isNotEmpty 第三 使用錯誤 基本的都知道 利用這個去冒泡 注意錯誤是一件事情 所以如果有FOR最好不要 try{ for(){ } } 要放在loop裡面 因為你每次for都要驗證 ## data contanier ![](https://i.imgur.com/tCMZlkE.png) 用來傳送數據 不會有方法 如何決定用哪個 Ex 今天連線資料庫 你只要知道連線跟段開 不用知道細部內容 這就物件 裡面幾乎都私有方法 只有一兩個對外的pubilc data contaniner 是全部都公開 基於此class去創造 ![](https://i.imgur.com/ejE2trd.png) ### object vs data container 這樣不是一個好寫法 ![](https://i.imgur.com/NURAgFJ.png) 為什麼呢 你除了處理資料庫 還要知道她能如何操作屬性 很怪 所以應該要寫成function 直接操作function 最好不要直接動到屬性 這樣就是所謂的封閉 如果你是要用物件 就要封閉 不要讓他直接抓到屬性 但如果你是 data container 數據傳送的才需要public屬性 一個要封閉 別人只要知道如何操作就好 不要讓別人知道太多細節 包含如何操作屬性 所以都私有屬性 但如果你是data container 你沒方法 直接操作屬性就好 因為他是數據傳送物件 ## class 拆分時機 「在函式裡,我們計算程式行數,來衡量函式的大小;在類別裡,我們使用不同的量測方式,我們計算職責的數量」 取自: Clean Code (p.152) ![](https://i.imgur.com/sYMHlyo.png) 聚合問題 用到越多屬性月不用拆分 也不是說只用到一個屬性就要拆分 就看個人 ## 羽化 (Emergence) Kent Beck's 簡單設計四守則 (Four Rules of Software Design) 遵守以下四個守則就能更容易使軟體善用「單一職責原則 (SRP)」及「相依性反向原則 (DIP)」 1. 執行完所有的測試 => Passes the tests 1. 表達程式設計師的本意 => Reveals intention (should be easy to understand) 1. 沒有重複的部分 => No duplication (DRY) 1. 最小化類別和方法的數量 => Fewest elements 取自: Clean Code (p.190) ### 得墨忒耳定律 law of dementer 當前的class不該過多專注其他物件的內部 ![](https://i.imgur.com/yCwlFVj.png) 正確做法 ![](https://i.imgur.com/UpPNHeG.png) 不要調用 雖然上面有一行在customer裡面用last了 但問題還是沒解決 所以最好的方法是 不要 跟他要求資訊使用 只要告訴其他類要做甚麼 ## SOLID 「SOLID 原則告訴我們該如何將函式和資料結構安排到類別中,及這些類別該如何相互關聯」 「一旦我們應用了 SOLID 原則,我們將與元件(Component)世界同行,接著再進入到高層架構(Architecture)的原則」 取自: Clean Architecture (pp.49-50) ## 單一 https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E5%96%AE%E4%B8%80%E8%81%B7%E8%B2%AC%E5%8E%9F%E5%89%87-single-responsibility-principle-c2c4bd9b4e79 嗯.. 這真的是一個是一個害死人的名稱,很多人會以為他意味每個模組都應該只做一件事。但真正的定義卻並非如此: 按照慣例,先上定義:“A class should have only one reason to change.” 翻譯成中文是:「一個模組應有且只有一個理由會使其改變。」 但是在 Clear Architecture 一書中,作者說到軟體系統改變是為了滿足使用者與利益相關者,所以我們應該把這理由套用到定義上,即變成: 「一個模組應只對唯一一個使用者或利益相關者負責。」 可是如果現在有多個使用者或利益相關者希望以相同的方式改變的話,我們不就會輕易地違反定義了嗎? 所以我們需要為一群希望以相同的方式改變的使用者或利益相關者一個定義,在這邊作者稱之為角色。 最終定義為:**「一個模組應只對唯一的一個角色負責。」** 每個模組的改變都只該影響到一個利益相關者(角色, Actor),模組的改變不該牽動到許多角色 ## 不適當的靜態宣告 (Inappropriate Static) 少用靜態方法。如果真的想要一個靜態函式,先確認你不可能想讓函式有多型行為 ## G23: 用多型取代 If/Else 或 Switch/Case (Prefer Polymorphism to If/Else or Switch/Case) One Switch 原則: 對於給定的選擇型態,不應有超過 1 個以上的 switch 敘述 ## 開放封閉 一個軟體製品在面對擴展時是開放的,且擴充時不應修改到原有的程式 https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E9%96%8B%E6%94%BE%E5%B0%81%E9%96%89%E5%8E%9F%E5%89%87-open-closed-principle-f7eaf921eb9c 一個子句是封閉的用來擴張 但因修改而關閉 錯誤示範 ![](https://i.imgur.com/oar1ez1.png) 應該要這樣 ![](https://i.imgur.com/CkcPhYd.png) 不要同一個class一直增長 要個別的分開 比較方便看 ![](https://i.imgur.com/p8BLctM.png) 在上圖中,整個架構的核心是 Interactor (業務邏輯層),他會被 Controller 與 DB 所依賴。而 Presenter 又會依賴於 Controller 。 **這層層的單向依賴有效於解耦。** ## 理式 子型態必須遵從父型態的行為進行設計。 按照 Design by Contract 設計方法,遵守 LSP 就是遵守以下三個條件: 1. 子型態的先決條件 (Preconditions) 不應被加強。 先決條件是指執行一段程式前必須成立的條件。使用者在使用子型態前,要確保子型態的先決條件不會比父型態的更強,但可以削弱。 如一個整數相加功能,輸入的參數必須為 2 個整體並回傳一個整數,且輸入的數字不能小於 0 及大於 50 (先決條件)。 ``` let sum = 0; // a,b 必須 >= 0 && <= 50 function add(int a, int b) { result = a + b; return result; } sum = add(1,5) ``` 子型態在覆寫這功能時,先決條件不能比父型態強。若父型態輸入的數字要求是「不能小於 0 及大於 50」,子型態輸入的數字則不能是「不能小於 0 及大於 51」,但可以是「不能小於 0 及大於 30」。 2 .子型態的後置條件 (Postconditions) 不應被削弱。 後置條件是指執行一段程式後必須成立的條件。使用者在使用子型態後,要確保子型態的後置條件不會比父型態的更弱,但可以加強其後置條件。 ``` let sum = 0; // a,b 必須 >= 0 && <= 50 function add(int a, int b) { result = a + b; // 回傳型態必須為 int return result; } // result 必須等於 sum sum = add(1,5) //加強條件 let sum2 = 0; sum2 = add(1,5) ``` 以相同的例子,這邊的後置條件是回傳的型別必須為 int。即子型態不能回傳非 int 型別,如最後把 int 轉成 String 再回傳。 子型態加強其後置條件,如上例,除了 result 必須等於 sum 外,子型態也可以加強條件,讓 result 也必須等於 sum2。 3. 父型態的不變條件 (Invariants) 必須被子型態所保留。 不變條件指不管在何時何地都不能改變,這是構成整個型態的重要條件。同樣地,子型態必須遵守父型態的不變條件,若然加以修改或不遵守,則會導致多型的重大失敗。 所以,只要 Override 有遵守以上三個原則,他就是符合了 LSP 原則。 ## 介面隔離 https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87-interface-segregation-principle-50f54473c79e 拆很細的interface 一個 function對一個好了 ## 依賴反轉 https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E4%BE%9D%E8%B3%B4%E5%8F%8D%E5%90%91%E5%8E%9F%E5%89%87-dependency-inversion-principle-a74ca045d776 不要再注入之後判斷 注入的只要實現同一個Interface就好 讓注入的決定他要注入啥 ![](https://i.imgur.com/PLOBldj.png) ## laravel文章 https://learnku.com/laravel/t/62638 ## 必包提取出來 提取一个重复回调作为变量 如果你有一个重复使用的回调函数,你可以提取它作为变量。 ``` // 你有一个很长的包含重复的回调函数 $results = Model::with('relationships') ->whereHas('relationships', function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }) ->withCount('relationships', function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }) ->get(); // 你可以提取它作为变量 $callback = function($query) use ($var1, $var2) { return $query->where('field1', $var1)->where('field2', $var2); }); $results = Model::with('relationships') ->whereHas('relationships', $callback) ->withCount('relationships', $callback) ->get(); ``` ### 能提高可读性的时候再创建变量 和上一条相反,**有时候一个值来自一整套复杂的计算**,因此创建一个变量,可以提高可读性,甚至连注释都省了。记住,上下文很重要,并且你编写代码的最终目标是让代码更具有可读性。 ``` // 坏的 Visit::create([ 'url' => $visit->url, 'referer' => $visit->referer, 'user_id' => $visit->userId, 'ip' => $visit->ip, 'timestamp' => $visit->timestamp, ])->conversion_goals()->attach($conversionData); ``` ``` // 好的 $visit = Visit::create([ 'url' => $visit->url, 'referer' => $visit->referer, 'user_id' => $visit->userId, 'ip' => $visit->ip, 'timestamp' => $visit->timestamp, ]); $visit->conversion_goals()->attach($conversionData); ``` ### 创建动作类 让我们来继续刚才的例子。有时,可以为某个动作单独创建一个类,这样会使代码更加整洁。模型封装的业务逻辑可以基于动作类,但是记得动作类不可太大。 ``` // 糟糕的方式 public function createInvoice(): Invoice { if ($this->invoice()->exists()) { throw new OrderAlreadyHasAnInvoice('Order already has an invoice.'); } return DB::transaction(function () use ($order) { $invoice = $order->invoice()->create(); $order->pushStatus(new AwaitingShipping); return $invoice; }); } // 优雅的方式 // 订单模型 public function createInvoice(): Invoice { if ($this->invoice()->exists()) { throw new OrderAlreadyHasAnInvoice('Order already has an invoice.'); } return app(CreateInvoiceForOrder::class)($this); } ``` ``` // 订单创建发票动作类 class CreatelnvoiceForOrder { public function _invoke(Order $order): Invoice { return DB::transaction(function () use ($order) { $invoice = $order->invoice()->create(); $order->pushStatus(new AwaitingShipping); return $invoice; }); } } ``` ### 使用数据传输对象 (DTO) 与其以特定顺序传递大量参数,不如考虑创建一个具有属性的对象来存储这些数据。 如果您发现某些行为可以移入此对象,则可以加分。 ``` // 糟糕的示例 public function log($url, $route_name, $route_data, $campaign_code, $traffic_source, $referer, $user_id, $visitor_id, $ip, $timestamp) { // ... } ``` ``` // 推荐的示例 public function log(Visit $visit) { // ... } class Visit { public string $url; public ?string $routeName; public array $routeData; public ?string $campaign; public array $trafficSource[]; public ?string $referer; public ?string $userId; public string $visitorId; public ?string $ip; public Carbon $timestamp; // ... } ``` ### 创建流式对象 你可以使用流式 API 来创建对象。使用单独的方法调用来逐渐添加数据,并且只要构造函数中的绝对最小值。正是因为每个方法都返回 $this ,你可以在任意一次调用后让整个流程停下来。 ``` Visit::make($url, $routeName, $routeData) ->withCampaign($campaign) ->withTrafficSource($trafficSource) ->withReferer($referer) // ... 等等 ``` ### 使用自定义集合 创建自定义集合可以更好地写出更富有表现力的语法。参考这个订单合计的示例: ``` // 坏的 $total = $order->products->sum(function (OrderProduct $product) { return $product->price * $product->quantity * (1 + $product->vat_rate); }); ``` ``` // 好的 $order->products->total(); class OrderProductCollection extends Collection { public function total() { $this->sum(function (OrderProduct $product) { return $product->price * $product->quantity * (1 + $product->vat_rate); }); } } ``` ### 创建单次使用的 trait 将方法添加到它所属的类中,比为每件事都创建操作类简洁得多,但是这会让类变得很大。尝试使用特征 traits,它主要是为了代码复用,但是单次使用的 trait 并没有错。 ``` class Order extends Model { use HasStatuses; // ... } trait HasStatuses { public static function bootHasStatuses() { ... } public static $statusMap = [ ... ]; public static $paymentStatusMap = [ ... ]; public static function getStatusId($status) { ... } public static function getPaymentStatusId($status): string { ... } public function statuses() { ... } public function payment_statuses() { ... } public function getStatusAttribute(): OrderStatusModel { ... } public function getPaymentStatusAttribute(): OrderPaymentStatus { ... } public function pushStatus($status, string $message = null, bool $notification = null) { ... } public function pushPaymentStatus($status, string $note = null) { ... } public function status(): OrderStatus { ... } public function paymentStatus(): PaymentStatus { ... } } ``` ### 为 where() 创建查询方法 使用更具有表现力的名字创建查询方法,而不是编写完整的 where()。这可以让你的代码(例如控制器)尽可能更少地与数据库结构产生耦合,并且可以让代码更清晰。 ``` // 不好的 Order::whereHas('status', function ($status) { return true$status->where('canceled', true); })->get(); ``` ``` // 好的 Order::whereCanceled()->get(); class Order extends Model { public function scopeWhereCanceled(Builder $query) { return $query>whereHas('status', function ($status) { return $status->where('canceled', true); }); } } ``` ### 不要使用模型方法来检索数据 如果你想要从模型中获取数据,可以创建一个访问器。保留以某种方式改变模型的方法。 ``` // 坏的 $user->gravatarUrl(); class User extends Authenticable { // ... public function gravatarUrl() { return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email))); } } ``` ``` // 好的 Suser->gravatar_url; class User extends Authenticable { // ... public function getGravatarUrlAttribute() { return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email))); } } ``` ### 使用短运算符 PHP 有很多很棒的操作符可以替代丑陋的 if 检查。 记住它们。 ``` // 糟糕的 // truthy test if (! $foo) { $foo = 'bar'; } // null test if (is_null($foo)) { $foo = 'bar'; } // isset test if (! isset($foo)) { $foo = 'bar'; } ``` ``` // 优雅的 // truthy test $foo = $foo ?: 'bar'; // null test $foo = $foo ?? 'bar'; // PHP 7.4 $foo ??= 'bar'; // isset test $foo = $foo ?? 'bar'; // PHP 7.4 $foo ??= 'bar'; ``` 决定您是否喜欢运算符周围的空格 在上面你可以看到我在 ! 和我要否定的值之间使用了空格。 我喜欢这个,因为它清楚地表明该值被否定了。 我在点周围做同样的事情。 可以按照您的喜好来清理您的代码。 ### 上下文问题 上面我说将业务逻辑转移到逻辑类 / 服务类是好的。 但上下文很重要, 这是来自流行的 “Laravel 最佳实践” 存储库的代码设计建议。 绝对没有理由将 3 行检查放入类中。 这只是过度设计。 ``` // 糟糕的示例 public function store(Request $request) { $this->articleService->handleUploadedImage($request->file('image')); } class ArticleService { public function handleUploadedImage($image) { if (!is_null($image)) { $image->move(public_path('images') . 'temp'); } } } ``` ``` // 推荐的示例 public function store(Request $request) { if ($request->hasFile('image')) { $request->file('image')->move(public_path('images') . 'temp'); } // ... } ``` ### 不要随机的拆开行,也不要让它们(一行代码)过长。 Don't split lines at random places, but don't make them too long either. Opening an array with [ and indenting the values tends to work well. Same with long function parameter values. 用 [ 打开数组并缩进值往往效果很好。与长函数参数值相同。 Other good places to split lines are chained calls and closures. 其他拆开行的好地方是链式调用和闭包 ![](https://i.imgur.com/eTBrhKx.png) ### 当可以直接传递值时,不要创建变量 ![](https://i.imgur.com/joHxUwB.png) 但過長的要拉出來 看下面 ### 在可以提高可读性时创建变量 Create variables when they improve readability The opposite of the previous tip. Sometimes the value comes from a complex call and as such, creating a variable improves readability & removes the need for a comment. 这与上一个提示相反。有时该值来自一个复杂的调用,因此,创建一个变量可以提高可读性并消除对注释的需要。 Remember that context matters & your end goal is readability 请记住上下文很重要,您的最终目标是可读性。 ![](https://i.imgur.com/BVo5I76.png) ### function 參數太多 When you see a function with a huge amount of parameters, it can mean: 当您可能到具有大量参数的函数时,它可能意味着: The function has too many responsibilities. Separate. The responsibilities are fine, but you should refactor the long signature. 函数的指责过多,分离。 指责没问题,但您应该重构长签名。 Below are two tactics for the fixing second case. 以下是修复第二种情况的两种策略。 #### Use Data Transfer Objects (DTOs). 使用数据传输对象(DTO) Rather than passing a huge amount of arguments in a specific order, consider creating an object with properties to store this data. 与其以特定顺序传递大量参数,不如考虑创建一个具有属性的对象来存储这些数据。 Bonus points if you can find that some behavior can be moved into to this object. 如果您发现某些行为可以移入此对象,则可以加分。 ![](https://i.imgur.com/HJ1ckMX.png) #### Create fluent objects. 创建流畅的对象。 You can also create objects with fluent APIs. Gradually add data by with separate calls, and only require the absolute minimum in the constructor. 您还可以使用流畅的 API 创建对象。 通过单独的调用逐步添加数据,并且只需要构造函数中的绝对最小值。 Each method will return $this, so you can stop at any call. 每个方法都将返回 $this,因此您可以在任何调用处停止。 ![](https://i.imgur.com/IT0GiYT.png) ### Use custom collections. 使用自定义集合。 Creating custom collections can be a great way to achieve more expressive syntax. Consider this example with order totals. 创建自定义集合是实现更具表现力的语法的好方法。 考虑这个带有订单总数的例子。 ![](https://i.imgur.com/p73KxFk.png) ### 不要使用缩写。 不要认为长变量 / 方法名称是错误的。 他们不是。 他们富有表现力。 Better to call a longer method than a short one and check the docblock to understand what it does 最好调用较长的方法而不是较短的方法并检查文档块以了解它的作用 Same with variables. Don't use nonsense 3-letters abbreviations 与变量相同。 不要使用无意义的 3 个字母缩写 ![](https://i.imgur.com/lLPmvs7.png) ## 为方法使用富有表现力的名称。 Rather than thinking “what can this object do”, think about “what can be done with this object”. Exceptions apply, such as with action classes, but this is a good rule of thumb. 与其思考 “这个对象能做什么”,不如想想 “这个对象能做什么”。 例外情况适用,例如操作类,但这是一个很好的经验法则。 ![](https://i.imgur.com/UEL6vIT.png) ### 创建一次性特征 Adding methods to classes where they belong is cleaner than creating action classes for everything, but it can make the classes grow big 向它们所属的类添加方法比为所有内容创建操作类更干净,但它会使类变大 Consider using traits. They're meant primarily for code reuse, but there's nothing wrong with single-use traits 考虑使用特征。 它们主要用于代码重用,但一次性使用特征并没有错 ![](https://i.imgur.com/JoEBEp8.png) ### 不要用別名 直接用命名空間 Import namespaces instead of using aliases Sometimes you may have multiple classes with the same name. Rather than importing them with an alias, import the namespaces. ![](https://i.imgur.com/ZE7FfYD.png) ### 多寫scope的where 可以重複用 Create query scopes for complex where()s. Rather than writing complex where() clauses, create query scopes with expressive names. This will make your e.g. controllers have to know less about the database structure and your code will be cleaner. ![](https://i.imgur.com/6sM3AUk.png) ### 建立訪問器 保留不要直接改變model Don’t use model methods to retrieve data If you want to retrieve some data from a model, create an accessor. Keep methods for things that change the model in some way. ![](https://i.imgur.com/8viJGDZ.png) ### 使用自定義配置文件 Use custom config files. You can store things like “results per page” in config files. Don’t add them to the app config file though. Create your own. In my e-commerce project, I use config/shop.php. ![](https://i.imgur.com/z0nXU2E.png) ## 有typehint 就不要用 註解的 Use docblocks only when they clarify things. Many people will disagree with this, because they do it. But it makes no sense. There's no point in using docblocks when they don't give any extra information. If the typehint is enough, don't add a docblock. That's just noise. ![](https://i.imgur.com/3W2ZTLM.png) ## 驗證規則集中起來 Have a single source of truth for validation rules. If you validate some resource's attributes on multiple places, you definitely want to centralize these validation rules, so that you don't change them in one place but forget about the other places. I often find myself keeping validation rules in a method on the model. This lets me reuse them wherever I may need - including in controllers or form requests. ![](https://i.imgur.com/cLGQ0WK.png) ## 編寫功能性代碼 Write functional code when it benefits you. Functional code can both clean things up and make them impossible to understand. Refactor common loops into functional calls, but don't write stupidly complex reduce()s just to avoid writing a loop. There's a use case for both. ![](https://i.imgur.com/EedByTR.png) ###### tags: `觀念重點區`