大學畢業4年-回顧和總結(6)-技術研發-重構之法

小雷FansUnion發表於2016-04-16
大部分的公司,開發專案都是作坊式的,沒有產品和專案的需求分析,進而做出技術架構和詳細設計。
很多人,聽到上級和老闆的一個想法,就開始寫程式碼,邊寫邊改,甚至推倒重來。

最終,導致的常見結果之一,專案程式碼混亂,新員工甚至老員工,對專案理解比較吃力。
如果你去改造程式碼,改好了,沒有任何功勞。改出問題了,領導、測試、產品,很可能會說你不行。
這一點,是讓很多程式設計師糾結的地方。

我個人還是傾向重構的,先熟悉專案總體環境,從易到難。
專案開發,從外部看,就是一個個的功能。
從內部看,不就是一個個函式和API嗎。
只要輸入和輸出是穩定的,就不會出現重大的失誤,導致牽一髮動全身的不良後果。

下面記錄一些,我個人自己揣摩的一些重構方法,閱讀《重構》這本書,也帶給了我一些啟發。

1.命名規範化
  包名、類名、方法名、變數名,取合適的英文單詞。
  比如在一個電商專案中。
  包名:com.company.shop.front.controller,前端系統的控制層
  類名:OrderController,處理訂單的控制器
  方法名:findById,根據id查詢物件,我個人更喜歡用get。
  變數名:List<String> memberIdList = new ArrayList<String>();
   會員id的list集合
   
  比較坑的命名:Object tagcode = map.get("tagCode");
  變數命名,沒有按照Java駝峰式命名規範來寫,“tagcode”和“tagCode”竟然同時存在。
  在Java內部程式碼變數名,如果錯了還有提示,在Freemarker和JSP等介面中,字串的值,用錯了,根本沒有提示。
 大小寫不統一的bug,還很難發現。
  
  命名要一致:Controller、Service、Dao等,同一個人和不同人的,儘可能遵循一定的標準,閱讀和修改其他人程式碼也更順利。
  
2.程式碼合理組織
  controller:控制器,響應請求,路由控制
  service:服務層,處理業務邏輯
  dao:資料訪問層
  model:資料庫模型
  bean:內部用的實體類
  util:工具程式碼
  interceptor:攔截器
  
  其它程式碼,可以按照功能等進行劃分,讓人一眼望去,就知道這個包下的程式碼,大致做了什麼事情。

3.專案合理組織
  合理拆分專案:
       移動端,mobile專案
Web前端,front專案
後端管理:backend專案
  合理服務拆分:
        商品服務系統:ProductService專案
會員服務系統:UserService專案
登入服務系統:LoginService專案
訂單服務系統:OrderService專案
  公共程式碼:Model模型、Util工具類

   聽說淘寶和京東的電商網站,有幾百上千個服務。
   
4.使用最小最恰當的作用域
   類,大多數類用的是public,public class ProductService,如果只是在包內部使用,可以去掉public。
   欄位,儘可能用private, private ProductService productService。如果需要,通過get和set方法,來獲得和修改值。
   方法/函式,對外被呼叫的用public,只在類的內部使用的,儘可能用private。
   public void  add(){
      doAdd();
   }
   
   private void doAdd(){
   
   }

   很多人,內部程式碼都搞成public,乍一眼看上去,還以為外部有呼叫。
   必須得看看依賴,才確定。
   對外暴露了過多的介面,不該被呼叫的被呼叫了。

5.常量提取
  "success",把作用一致並且相同的字串,提取成常量,統一管理。
  其中一種方式:
public class FrontConst {

	public static final String COOKIE_LOGINNAME = "loginNameCookie";
	
	public static final String COOKIE_PASSWORD = "passWordCookie";
	
}


6.重複程式碼提取和封裝
  業務程式碼,提取成私有方法,內部重複使用。
  工具程式碼,提取到工具類中,可以複用,比如日期處理、把list轉換成map。
 
 public class BizUtil {
	/**
	 * 把1個集合,轉換成Map。用法示例:Map<String, Dictionary> dictionaryMap = BizUtil.listToMap("BIANMA", dictionarys);
	 * @param keyName 集合元素唯一欄位的名稱
	 * @param list 集合元素
	 * @return map
	 */
	public static <K, V> Map<K, V> listToMap(final String keyName, List<V> list) {
		if(CollectionUtils.isEmpty(list)){
			return null;
		}
		return Maps.uniqueIndex(list, new Function<V, K>() {
			@Override
			public K apply(V v) {
				try {
					return (K)PropertyUtils.getSimpleProperty(v, keyName);
				} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
					e.printStackTrace();
					return null;
				}
			}
		});
	}

	/**
	 * 把1個集合,轉換成Map。用法示例:Map<String, Dictionary> dictionaryMap =
	 * BizUtil.listToMap(String.class, Dictionary.class, "BIANMA", dictionarys);
	 * 
	 * @param k
	 *            Map的key的class
	 * @param v
	 *            Map的value的class,集合list元素的型別
	 * @param keyName
	 *            集合元素唯一欄位的名稱
	 * @param list
	 *            集合元素
	 * @return map
	 */
	//這種方式,廢棄了,需要多傳2個引數
	public static <K, V> Map<K, V> listToMap(Class<K> k, Class<V> v,
			String keyName, List<V> list) {
		Map<K, V> map = Maps.newHashMap();
		if (CollectionUtils.isNotEmpty(list)) {
			for (V val : list) {
				try {
					PropertyDescriptor pd = new PropertyDescriptor(keyName, v);
					Method getMethod = pd.getReadMethod();// 獲得get方法
					Object o = getMethod.invoke(val);// 執行get方法返回一個Object
					if (o != null && o.getClass().equals(k)) {
						map.put((K) o, val);
					}
				} catch (IllegalAccessException | IllegalArgumentException
						| InvocationTargetException | IntrospectionException e) {
					e.printStackTrace();
				}

			}
		}
		return map;
	}

7.函式拆分
  1個函式,有5個引數,有2種不同的使用場景。
  同一個函式完成了2件不同的事情。
  void add(int a,int b,int c);
  換成
  void addOrder(int a,int b);
  void addProduct(int c)。
  
8.函式合併
  2個函式,完成了類似的事情,簡化成1個,

  函式是拆分,還是合併,要具體分析。
  
9.單一職責原則
  一個專案、一個模組、一個類、一個函式,完成一件事。
  如果完成了多件事,需要進行拆分成多個獨立的函式,至少內部需要進行拆分。
  比如:
  void add(int a,int b,int c)
  可以拆分成
  void add1(int a,int b)和void add2(int c)
  
  也可以拆分成
  void add(int a,int b,int c){
    add1(a,b);
add2(c);
  }

10.控制類和函式的行數
   一個類,如果函式太多,往往是承載了太多的功能。
   把不緊密相關的功能,堆積在了一起。
   
   一個方法,如果程式碼超過了100行,存在問題的可能性就增大了很多。
   在我自身的經歷中,很多同事的函式,只要超過100行,一眼望去就能發現問題。
   
   另外,函式程式碼過多,修改bug和新增業務邏輯的時候,很容易引入新的問題。
   因此,函式拆分、降低作用域,可以保證過去穩定的程式碼邏輯,不會有改動。
   
11.提前返回
  先檢查錯誤,如果有問題,直接返回,以免巢狀過深。
public String add(){
		Member member = this.getMember();
		if(StringUtils.isBlank(...){
			return error("地址資訊有誤!");
		}
}

經常出現下面的情況
   if(...){
       if(...){
      if(...){
  
  }
   }
   }
  return ...;
  
12.定義列舉
  列舉是常量的進一步封裝。
public enum OrderPayStatusEnum {
	NO("0", "未支付"), YES("1", "已支付");

	private String code;
	private String remark;

	OrderPayStatusEnum(String code, String remark) {
		this.code = code;
		this.remark = remark;
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getRemark() {
		return remark;
	}

	public void setRemark(String remark) {
		this.remark = remark;
	}

	public static String getPayStatus(String code){
		if(StringUtils.isEmpty(code)){
			return null;
		}
		if(code.equals(NO.getCode())){
			return NO.getRemark();
		}else if(code.equals(YES.getCode())){
			return YES.getRemark();
		}
		return null;
	}
}


13.統一API互動介面
 後端和移動端,後端和Web前端的互動,都是Result的json字串。
 後端,所有請求,統一都是返回Result。
public class Result {
    private Integer code;
    private String desc;
    private Object data;
}
 多人開發的時候,如果沒有架構師之類的角色,各自為戰,真是亂套。

14.模組化和依賴
  大多數專案,都會有諸如郵件、簡訊驗證碼、登入服務、圖片雲服務等。
  把配置檔案單獨拿出來,可以手動通過Spring的配置檔案,配置bean。
  如果需要,引入配置檔案,不引入,程式碼也不會報錯。
  
  spring-mail-config.xml
 
<context:property-placeholder location="file:${config_path}/config/mail.properties" ignore-unresolvable="true" />
  <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
		<property name="host" value="${mailServerHost}" />
		<property name="port" value="${mailServerPort}" />
		<property name="javaMailProperties">
			<props>
				<prop key="mail.smtp.auth">true</prop>
				<prop key="mail.smtp.timeout">25000</prop>
			</props>
		</property>
		<!-- 傳送者使用者名稱 -->
		<property name="username" value="${mailUserName}" />
		<!-- 傳送者密碼 -->
		<property name="password" value="${mailPassword}" />
		<property name="defaultEncoding" value="UTF-8" />
	</bean>

	<bean id="mailClientServer" class="com.shop.common.mail.MailClientServer">
		<property name="javaMailSender" ref="javaMailSender" />
		<property name="configuration" ref="freeMarkerConfigurationFactory" />
		<property name="mailFromAddress" value="${mailFromAddress}" />
	</bean>

  還比如,我們們的圖片用的阿里雲的OSS。
  統一封裝。
  配置檔案
  oss.properties
  oss.xml
 
   	<context:property-placeholder
		location="file:${config_path}/config/oss.properties"
		ignore-unresolvable="true" />
	<bean id="ossConfig" class="com.shop.common.oss.OssConfig">
		<property name="endpoint" value="${oss.endpoint}" />
		<property name="bucketName" value="${oss.bucketName}" />
		<property name="accessKeyId" value="${oss.accessKeyId}" />
		<property name="accessKeySecret" value="${oss.accessKeySecret}" />
		<property name="imgDomain" value="${oss.imgDomain}" />
		<property name="fileDomain" value="${oss.fileDomain}" />
	</bean>


  Java程式碼
  public class OssUtil {}
  
15.程式碼複用
  程式碼複用,是一個廣泛的話題。
  前文提到的列舉、常量、函式拆分,目的之一就是為了方便程式碼複用。
  一個優秀的程式設計師,抵得上十個差的程式設計師,其中一個原因就是“程式碼複用多”。
  總是寫重複的程式碼,把程式設計師這個智力密集型工作,幹成了體力密集型,早晚被累死。
  
  梳理業務需求、做好業務方面的架構設計。
  另外,技術方面的架構設計,完全掌握在技術人員手中,對業務的依賴不大。
  開發一個功能時,先想好思路,技術方案,再寫程式碼。
  
  架構複用、設計複用、工具類、列舉、常量。
  另外還有一種,非常常見的,流程複用。
  
  前端系統:
    1.構造url和引數
2.執行請求,獲得資料
3.根據狀態碼,執行不同的操作
4.正常狀態,渲染資料,或執行動作
  移動端:
    流程類似
  後端:
    1.穩定並且統一的API介面
2.業務邏輯封裝
無論內部怎麼變,介面穩定,就不會影響Web前端和移動端。
  在合適的時機,內部專案拆分,專案服務化。

  重構的技巧,還是有很多的,更多還是看個人對程式設計的理解。
  對於有經驗的程式設計師來說,《重構》是一本不錯的書,可以用來快速加深平時對專案程式碼的梳理。
  對於新手來說,《重構》和設計模式,這種書,受益偏小。

小雷-一個有獨立見解的年輕人
2016年4月16日~清明節剛過,五一又要來了
湖北-武漢~聽說最近有幾條公交專線要開通了

最近,有好多好多的想法和話題要寫,時間不太夠。
更準確來說, 時間是有的,寫文章,也要看心情。
寫一篇嚴肅一點的長篇大論,一般都需要1小時以上。
在寫作之前,大腦裡面一直在不斷地“打草稿”,寫的過程中,需要不斷髮散,要有條理,還要嚴謹,站得住腳。

一些問題的根源,也許是我的身價還是太低了,更多時間忙著搞錢,一些有價值的事情,只能慢慢地去做。 

相關文章