【前端】重構,有品位的代碼 06── 重新組織數據
本文轉載自微信公眾號「前端萬有引力」,作者一川。轉載本文請聯系前端萬有引力公眾號。
重新組織數據
常用的重新組織數據方式有:
- 拆分變量
- 字段改名
- 以查詢取代派生變量
- 將引用對象改為值對象
- 將值對象改為引用對象
1. 拆分變量
變量在代碼中有著不同的用途,而有些用途毀導致臨時變量被多次賦值,比如:循環語句中的變量以及結果收集變量(通過整個函數運算構成的值)。當然,還有許多變量只被賦值一次,用于保存冗長代碼的運算結果,如果其承擔多個責任(被賦值多次),那么就意味著需要對其進行拆分為多個單獨責任的變量。
其實,就是在待分解變量的聲明及其初次被賦值處,修改其名稱,盡量將其變量聲明設置為不可修改(其實就是使用const)。以該變量的第二次賦值動作為界,修改此時對該變量的所有引用,讓其引用新變量。
原始代碼:
- let height = 2, width = 3;
- let temp = 4 * ( height + width);
- console.log(temp);
- temp = height * width;
- console.log(temp);
重構代碼:
- const height = 2, width = 3;
- const perimeter = 4 * ( height + width);
- console.log(perimeter);
- const area = height * width;
- console.log(area);
2. 字段改名
對于程序中廣泛使用的記錄結構,數據結構有助于閱讀理解代碼邏輯,其字段的命名顯得格外重要。數據結構作用無非就是讓代碼更加簡潔有條理,但事實上隨著時間的推移,我們對代碼的理解更加透徹,此時就會對于某些字段根據作用和業務進行改名。
來看看如何對字段進行改名,首先判斷當前記錄的作用域的范圍,當其作用域小時可以直接修改所有該字段的代碼。當然如果作用域范圍大時,可以先對記錄進行封裝,在對象內部對私有字段進行改名,并對應調整內部訪問該字段的函數。
原始代碼:
- class User{
- constructor(data){
- this._name = data.name;
- this._age = data.age;
- }
- get name(){
- return this._name;
- }
- set name(newName){
- this._name = newName;
- }
- get age(){
- return this._age;
- }
- set age(newAge){
- this._age = newAge;
- }
- }
- const yichuan = new User({name:"yichuan",age:18});
重構代碼:
- class User{
- constructor(data){
- this._title = data.title;
- this._age = data.age;
- }
- get title(){
- return this._title;
- }
- set title(newTitle){
- this._name = newTitle;
- }
- get age(){
- return this._age;
- }
- set age(newAge){
- this._age = newAge;
- }
- }
是不是很簡單,就是對應的改變字段名字而已。
3. 以查詢取代派生變量
要知道數據可變時軟件犯錯的源頭,對數據的修改常導致代碼各部分耦合,即在某處進行修改后,卻對另外一處造成意想不到的災難。當然,完全消滅可變數據這是不現實的,所以應該將可變數據的作用域縮小到可控制范圍內。
計算能夠清晰地表達數據的含義,能夠最大化避免源數據修改時忘更新派生變量的情況。有些變量很容易隨時被計算,如果能夠將其消除此變量,那么也就朝著消除數據可變性更進一步。如果計算的源數據是不可變的,也可以強制計算得到的結果是不可變的,這樣就不必消除計算得到的派生變量。因此,可以根據源數據生成新數據結構的變換操作保持不變,即使已經將其替換為計算操作。
看看具體例子,其原始代碼如下:
- get discountedTotal(){
- return this._discountedTotal;
- }
- set discountedTotal(discount){
- const old = this._discount;
- this._discount = discount;
- this._discountedTotal += old - discount;
- }
重構代碼:
- get discountedTotal(){
- return this._baseTotal - this._discount;
- }
- set discountedTotal(discount){
- this._discount = discount;
- }
4. 將引用對象改為值對象
在將一個對象嵌入到另一個對象時,位于內部的這個對象被稱為引用對象,亦可稱為值對象。當然兩者最明顯差異在于如何更新內部對象的屬性:如果將內部對象視為引用對象,在更新其屬性時可以保留原對象不動,只更新內部對象的屬性;如果將內部對象視為值對象,可以替換整個內部對象,對新對象可以設置新的屬性值,
在實際操作中,得先檢查重構目標是否是不可變對象,或者是否可修改不可變對象,再逐步去除所有設置函數。提供一個基于值的相等性判斷函數,在其中使用對象的字段。
原始代碼:
- class Product{
- applyDiscount(arg){
- this._price.aount -= arg;
- }
- }
重構代碼:
- class Product{
- applyDiscount(arg){
- this._price = new Money(this._price.amount - arg, this._price.currency)
- }
- }
5. 將值對象改為引用對象
在數據結構中有可能包含多個記錄,這些記錄都關聯到同個邏輯數據結構。同份數據復制多次可能造成困擾,因為會導致內存占用問題,會導致性能問題。如果共享數據需要更新,可以將多份數據副本變成單一引用,可以將數據修改反映到變化中。
在為相關對象創建一個倉庫,確保構造函數可以找到關聯對象的正確實例,修改宿主對象的構造函數,令其從倉庫中獲取關聯對象。
- // 原始代碼
- let customer = new Customer(data);
- // 重構代碼
- let customer = customerRespository.get(data.id);
小結
在本文中,主要介紹了如何重新組織數據,可以將代碼中的數據結構進行改善。
參考文章
《重構──改善既有代碼的設計(第2版)》


























