整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          在JavaScript中編寫更好的條件的5個技巧

          在JavaScript中編寫更好的條件的5個技巧

          .對多個條件使用Array.includes

          我們來看看下面的例子:

          // condition
          function test(fruit) {
           if (fruit=='apple' || fruit=='strawberry') {
           console.log('red');
           }
          }
          

          乍一看,上面的例子看起來不錯。然而,如果我們得到更多的紅色水果,說的cherry和cranberries?我們是否會更多地擴展聲明||?

          我們可以使用(Array.includes)重寫上面的條件Array.includes

          function test(fruit) {
           // extract conditions to array
           const redFruits=['apple', 'strawberry', 'cherry', 'cranberries'];
           if (redFruits.includes(fruit)) {
           console.log('red');
           }
          }
          

          我們將red fruits(條件)提取到數(shù)組中。通過這樣做,代碼看起來更整潔。


          2.較少嵌套,早退

          讓我們擴展前面的示例以包含另外兩個條件:

          • 如果沒有提供水果,拋出錯誤
          • 如果超過10,則接受并打印水果數(shù)量。
          function test(fruit, quantity) {
           const redFruits=['apple', 'strawberry', 'cherry', 'cranberries'];
           // condition 1: fruit must has value
           if (fruit) {
           // condition 2: must be red
           if (redFruits.includes(fruit)) {
           console.log('red');
           // condition 3: must be big quantity
           if (quantity > 10) {
           console.log('big quantity');
           }
           }
           } else {
           throw new Error('No fruit!');
           }
          }
          // test results
          test(null); // error: No fruits
          test('apple'); // print: red
          test('apple', 20); // print: red, big quantity
          

          看看上面的代碼,我們有:

          • 1 if / else語句過濾掉無效條件
          • 3級嵌套if語句(條件1,2和3)

          我個人遵循的一般規(guī)則是發(fā)現(xiàn)無效條件時提前返回

          /_ return early when invalid conditions found _/
          function test(fruit, quantity) {
           const redFruits=['apple', 'strawberry', 'cherry', 'cranberries'];
           // condition 1: throw error early
           if (!fruit) throw new Error('No fruit!');
           // condition 2: must be red
           if (redFruits.includes(fruit)) {
           console.log('red');
           // condition 3: must be big quantity
           if (quantity > 10) {
           console.log('big quantity');
           }
           }
          }
          

          通過這樣做,我們有一個較少級別的嵌套語句。這種編碼風格很好,特別是當你有很長的if語句時(想象你需要滾動到最底層才知道有一個else語句,而不是很酷)。

          如果通過反轉條件并提前返回,我們可以進一步減少嵌套。請查看下面的條件2,看看我們是如何做到的:


          /_ return early when invalid conditions found _/
          function test(fruit, quantity) {
           const redFruits=['apple', 'strawberry', 'cherry', 'cranberries'];
           if (!fruit) throw new Error('No fruit!'); // condition 1: throw error early
           if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red
           console.log('red');
           // condition 3: must be big quantity
           if (quantity > 10) {
           console.log('big quantity');
           }
          }
          

          通過反轉條件2的條件,我們的代碼現(xiàn)在沒有嵌套語句。當我們有很長的邏輯時,這種技術非常有用,我們希望在條件不滿足時停止進一步的處理。

          但是,這樣做并不是一件難事。問問自己,這個版本(沒有嵌套)比前一個更好/更可讀(條件2嵌套)?

          對我來說,我只是把它留作以前的版本(條件2嵌套)。這是因為:

          • 代碼簡短直接,嵌套if更清晰
          • 反轉條件可能會引發(fā)更多的思考過程(增加認知負荷)

          因此,始終旨在盡早減少筑巢和回歸,但不要過度。如果您感興趣,有一篇文章和StackOverflow討論會進一步討論這個主題:

          • 避免其他,早期由蒂姆奧克斯利回歸
          • StackOverflow討論 if / else編碼風格

          3.使用默認功能參數(shù)和解構

          我想下面的代碼可能看起來很熟悉,我們總是需要檢查null/ undefined值并在使用JavaScript時分配默認值:

          function test(fruit, quantity) {
           if (!fruit) return;
           const q=quantity || 1; // if quantity not provided, default to one
           console.log(`We have ${q} ${fruit}!`);
          }
          //test results
          test('banana'); // We have 1 banana!
          test('apple', 2); // We have 2 apple!
          

          實際上,我們可以q通過分配默認函數(shù)參數(shù)來消除變量。

          function test(fruit, quantity=1) { // if quantity not provided, default to one
           if (!fruit) return;
           console.log(`We have ${quantity} ${fruit}!`);
          }
          //test results
          test('banana'); // We have 1 banana!
          test('apple', 2); // We have 2 apple!
          

          更簡單直觀不是嗎?請注意,每個參數(shù)都有自己的默認函數(shù)參數(shù)。例如,我們也可以指定默認值fruit:function test(fruit='unknown', quantity=1)。

          如果我們fruit是一個對象怎么辦?我們可以指定默認參數(shù)嗎?

          function test(fruit) { 
           // printing fruit name if value provided
           if (fruit && fruit.name) {
           console.log (fruit.name);
           } else {
           console.log('unknown');
           }
          }
          //test results
          test(undefined); // unknown
          test({ }); // unknown
          test({ name: 'apple', color: 'red' }); // apple
          

          看看上面的例子,我們想要打印水果名稱,如果它可用,或者我們將打印未知。我們可以避免fruit && fruit.name使用默認函數(shù)參數(shù)和破壞進行條件檢查。

          // destructing - get name property only
          // assign default empty object {}
          function test({name}={}) {
           console.log (name || 'unknown');
          }
          //test results
          test(undefined); // unknown
          test({ }); // unknown
          test({ name: 'apple', color: 'red' }); // apple
          

          由于我們只需要name來自水果的屬性,我們可以使用構造參數(shù){name},然后我們可以name在代碼中使用變量代替fruit.name。

          我們還將空對象指定{}為默認值。如果我們不這樣做,你在執(zhí)行行時會出錯test(undefined)- Cannot destructure property name of 'undefined' or 'null'.因為nameundefined中沒有屬性。

          如果您不介意使用第三方庫,有幾種方法可以減少空檢查:

          • 使用Lodash獲取功能
          • 使用Facebook開源的idx庫(與Babeljs)

          以下是使用Lodash的示例:

          // Include lodash library, you will get _
          function test(fruit) {
           console.log(__.get(fruit, 'name', 'unknown'); // get property name, if not available, assign default value 'unknown'
          }
          //test results
          test(undefined); // unknown
          test({ }); // unknown
          test({ name: 'apple', color: 'red' }); // apple
          

          您可以在此處運行演示代碼。此外,如果您是功能編程(FP)的粉絲,您可以選擇使用Lodash fp,Lodash的功能版本(方法更改為get或getOr)。

          4.支持Map / Object Literal而不是Switch語句

          讓我們看看下面的例子,我們想根據(jù)顏色打印水果:

          function test(color) {
           // use switch case to find fruits in color
           switch (color) {
           case 'red':
           return ['apple', 'strawberry'];
           case 'yellow':
           return ['banana', 'pineapple'];
           case 'purple':
           return ['grape', 'plum'];
           default:
           return [];
           }
          }
          //test results
          test(null); // []
          test('yellow'); // ['banana', 'pineapple']
          

          上面的代碼似乎沒有錯,但我覺得它很冗長。使用具有更清晰語法的object literal可以實現(xiàn)相同的結果:

          // use object literal to find fruits in color
           const fruitColor={
           red: ['apple', 'strawberry'],
           yellow: ['banana', 'pineapple'],
           purple: ['grape', 'plum']
           };
          function test(color) {
           return fruitColor[color] || [];
          }
          

          或者,您可以使用Map來實現(xiàn)相同的結果:

          // use Map to find fruits in color
           const fruitColor=new Map()
           .set('red', ['apple', 'strawberry'])
           .set('yellow', ['banana', 'pineapple'])
           .set('purple', ['grape', 'plum']);
          function test(color) {
           return fruitColor.get(color) || [];
          }
          

          Map是自ES2015以來可用的對象類型,允許您存儲鍵值對。

          我們應該禁止使用switch語句嗎?不要局限于此。就個人而言,我盡可能使用對象文字,但我不會設置硬規(guī)則來阻止它,使用對你的場景有意義的。

          Todd Motto有一篇文章深入研究switch語句與對象文字,你可以在這里閱讀。

          TL; DR; 重構語法

          對于上面的例子,我們實際上可以重構我們的代碼以獲得相同的結果Array.filter。

           const fruits=[
           { name: 'apple', color: 'red' }, 
           { name: 'strawberry', color: 'red' }, 
           { name: 'banana', color: 'yellow' }, 
           { name: 'pineapple', color: 'yellow' }, 
           { name: 'grape', color: 'purple' }, 
           { name: 'plum', color: 'purple' }
          ];
          function test(color) {
           // use Array filter to find fruits in color
           return fruits.filter(f=> f.color==color);
          }
          

          總有不止一種方法可以達到相同的效果。我們用相同的例子展示了4。編碼很有趣!

          5.對所有/部分標準使用Array.every和Array.some

          、Best Practice

          注釋應該聲明代碼的高層次意圖,而非明顯的細節(jié)

          反例

           /**
           * generate signature by code, the algorithm is as follows:
           * 1.sort the http params, if you use java, you can easily use treeMap data structure
           * 2.join the param k-v
           * 3.use hmac-sha1 encrypt the specified string
           *
           * @param params request params
           * @param secret auth secret
           * @return secret sign
           * @throws Exception exception
           */
           public static String generateSignature(Map<String, Object> params, String secret) throws Exception {
           final StringBuilder paramStr=new StringBuilder();
           final Map<String, Object> sortedMap=new TreeMap<>(params);
           for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
           paramStr.append(entry.getKey());
           paramStr.append(entry.getValue());
           }
           Mac hmac=Mac.getInstance("HmacSHA1");
           SecretKeySpec sec=new SecretKeySpec(secret.getBytes(), "HmacSHA1");
           hmac.init(sec);
           byte[] digest=hmac.doFinal(paramStr.toString().getBytes());
           return new String(new Hex().encode(digest), "UTF-8");
           }
          

          說明

          上文方法用于根據(jù)參數(shù)生成簽名,注釋中詳細描述了簽名算法的實現(xiàn)步驟,這其實就是過度描述代碼明顯細節(jié)

          正例

           /**
           * generate signature by params and secret, used for computing signature for http request.
           *
           * @param params request params
           * @param secret auth secret
           * @return secret sign
           * @throws Exception exception
           */
           public static String generateSignature(Map<String, Object> params, String secret) throws Exception {
           final StringBuilder paramStr=new StringBuilder();
           final Map<String, Object> sortedMap=new TreeMap<>(params);
           for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
           paramStr.append(entry.getKey());
           paramStr.append(entry.getValue());
           }
           Mac hmac=Mac.getInstance("HmacSHA1");
           SecretKeySpec sec=new SecretKeySpec(secret.getBytes(), "HmacSHA1");
           hmac.init(sec);
           byte[] digest=hmac.doFinal(paramStr.toString().getBytes());
           return new String(new Hex().encode(digest), "UTF-8");
           }
          

          總結

          • 注釋一定是表達代碼之外的東西,代碼可以包含的內容,注釋中一定不要出現(xiàn)
          • 如果有必要注釋,請注釋意圖(why),而不要去注釋實現(xiàn)(how),大家都會看代碼

          在文件/類級別使用全局注釋來解釋所有部分如何工作

          正例

          /**
           * <p>
           * Helpers for {@code java.lang.System}.
           * </p>
           * <p>
           * If a system property cannot be read due to security restrictions, the corresponding field in this class will be set
           * to {@code null} and a message will be written to {@code System.err}.
           * </p>
           * <p>
           * #ThreadSafe#
           * </p>
           *
           * @since 1.0
           * @version $Id: SystemUtils.java 1583482 2014-03-31 22:54:57Z niallp $
           */
          public class SystemUtils {}
          

          總結

          通常每個文件或類都應該有一個全局注釋來概述該類的作用

          公共api需要添加注釋,其它代碼謹慎使用注釋

          反例

          /**
           *
           * @author yzq
           * @date 2017
           */
          public interface KeyPairService {
           PlainResult<KeyPairInfoModel> createKeyPair(KeyPairCreateParam createParam);
          }
          

          說明

          以上接口提供dubbo rpc服務屬于公共api,以二方包的方式提供給調用方,雖然代碼簡單缺少了接口概要描述及方法注釋等基本信息。

          正例

          /**
           * dubbo service: key pair rpc service api.
           *
           * @author yzq
           * @date 2017/02/22
           */
          public interface KeyPairService {
           /**
           * create key pair info.
           *
           * @param createParam key pair create param
           * @return BaseResult
           */
           PlainResult<KeyPairInfoModel> createKeyPair(KeyPairCreateParam createParam);
          }
          

          總結

          公共api一定要有注釋,類文件使用類注釋,公共接口方法用方法注釋

          在注釋中用精心挑選的輸入輸出例子進行說明

          正例

           /**
           * <p>Checks if CharSequence contains a search character, handling {@code null}.
           * This method uses {@link String#indexOf(int)} if possible.</p>
           *
           * <p>A {@code null} or empty ("") CharSequence will return {@code false}.</p>
           *
           * <pre>
           * StringUtils.contains(null, *)=false
           * StringUtils.contains("", *)=false
           * StringUtils.contains("abc", 'a')=true
           * StringUtils.contains("abc", 'z')=false
           * </pre>
           *
           * @param seq the CharSequence to check, may be null
           * @param searchChar the character to find
           * @return true if the CharSequence contains the search character,
           * false if not or {@code null} string input
           * @since 2.0
           * @since 3.0 Changed signature from contains(String, int) to contains(CharSequence, int)
           */
           public static boolean contains(final CharSequence seq, final int searchChar) {
           if (isEmpty(seq)) {
           return false;
           }
           return CharSequenceUtils.indexOf(seq, searchChar, 0) >=0;
           }
          

          總結

          對于公共的方法尤其是通用的工具類方法提供輸入輸出的例子往往比任何語言都有力

          注釋一定要描述離它最近的代碼

          反例

           private Map<String, String> buildInstanceDocumentMap(String version, String instanceId) {
           Map<String, String> instanceDocumentMap=Maps.newLinkedHashMap();
           Map<String, String> instanceDocumentMapMetadataPart=metaDataService.getInstanceDocument(instanceId, version,
           instanceDocumentMetaKeys);
           instanceDocumentMap.putAll(instanceDocumentMapMetadataPart);
           //the map must remove the old key for instance type
           instanceDocumentMap.put("instance-type", instanceDocumentMap.get("instance/instance-type"));
           instanceDocumentMap.remove("instance/instance-type");
           return instanceDocumentMap;
           }
          

          說明

          該方法有一行代碼從map里刪除了一個數(shù)據(jù),注釋放在了put調用之前,而沒有直接放在remove之前

          正例

           private Map<String, String> buildInstanceDocumentMap(String version, String instanceId) {
           Map<String, String> instanceDocumentMap=Maps.newLinkedHashMap();
           Map<String, String> instanceDocumentMapMetadataPart=metaDataService.getInstanceDocument(instanceId, version,
           instanceDocumentMetaKeys);
           instanceDocumentMap.putAll(instanceDocumentMapMetadataPart);
           instanceDocumentMap.put("instance-type", instanceDocumentMap.get("instance/instance-type"));
           //the map must remove the old key for instance type
           instanceDocumentMap.remove("instance/instance-type");
           return instanceDocumentMap;
           }
          

          總結

          注釋要放在距離其描述代碼最近的位置

          注釋一定要與代碼對應

          反例

           /**
           * 根據(jù)hash過后的id生成指定長度的隨機字符串, 且長度不能超過16個字符
           * 
           * @param len length of string
           * @param id id
           * @return String
           */
           public static String randomStringWithId(int len, long id) {
           if (len < 1 || len > 32) {
           throw new UnsupportedOperationException("can't support to generate 1-32 length random string");
           }
           //use default random seed
           StringBuffer sb=new StringBuffer();
           long genid=id;
           for (int i=0; i < len; i++) {
           long pos=genid%32 ;
           genid=genid>>6;
           sb.append(RANDOM_CHAR[(int) pos]);
           }
           return sb.toString();
           }
          

          說明

          注釋中說明生成隨機字符串的長度不能超過16字符,實際代碼已經(jīng)修改為32個字符,此處注釋會產生誤導讀者的副作用

          正例

           /**
           * 根據(jù)hash過后的id生成指定長度的隨機字符串
           * 
           * @param len length of string
           * @param id id
           * @return String
           */
           public static String randomStringWithId(int len, long id) {
           if (len < 1 || len > 32) {
           throw new UnsupportedOperationException("can't support to generate 1-32 length random string");
           }
           //use default random seed
           StringBuffer sb=new StringBuffer();
           long genid=id;
           for (int i=0; i < len; i++) {
           long pos=genid%32 ;
           genid=genid>>6;
           sb.append(RANDOM_CHAR[(int) pos]);
           }
           return sb.toString();
           }
          

          總結

          • 注釋一定要與代碼對應,通常代碼變化對應的注釋也要隨之改變
          • 若非必要慎用注釋,注釋同代碼一樣需要維護更新

          一定要給常量加注釋

          反例

          /**
           * define common constants for ebs common component.
           *
           * Author: yzq Date: 16/7/12 Time: 17:44
           */
          public final class CommonConstants {
           /**
           * keep singleton
           */
           private CommonConstants() {}
           public static final String BILLING_BID="26842";
           public static final int BILLING_DOMAIN_INTEGRITY_VALID=1;
           public static final int BILLING_READYFLAG_START=0;
          }
          

          正例

          /**
           * define common constants for ebs common component.
           *
           * Author: yzq Date: 16/7/12 Time: 17:44
           */
          public final class CommonConstants {
           /**
           * keep singleton
           */
           private CommonConstants() {}
           /**
           * oms client bid.
           */
           public static final String BILLING_BID="26842";
           /**
           * oms billing domain integrity true.
           */
           public static final int BILLING_DOMAIN_INTEGRITY_VALID=1;
           /**
           * oms billing readyflag start.
           */
           public static final int BILLING_READYFLAG_START=0;
          }
          

          總結

          • 給每一個常量加一個有效的注釋

          巧用標記(TODO,F(xiàn)IXME,HACK)

          • TODO 有未完成的事項
          • FIXME 代碼有已知問題待修復
          • HACK 表示代碼有hack邏輯

          示例

           public static String randomStringWithId(int len, long id) {
           // TODO: 2018/6/11 需要將len的合法范圍抽象
           if (len < 1 || len > 32) {
           throw new UnsupportedOperationException("can't support to generate 1-32 length random string");
           }
           //use default random seed
           StringBuffer sb=new StringBuffer();
           long genid=id;
           for (int i=0; i < len; i++) {
           long pos=genid%32 ;
           genid=genid>>6;
           sb.append(RANDOM_CHAR[(int) pos]);
           }
           return sb.toString();
           }
          

          配置標記

          可以擴展IDE修改標記的配置,比如加入解決人,關聯(lián)缺陷等信息,以IDEA為例修改入口如下:

          總結

          • 巧用TODO、FIXME、HACK等注解標識代碼
          • 及時處理所有標識代碼,忌濫用

          適當添加警示注釋

          正例

           private BaseResult putReadyFlag(BillingDataContext context, Integer readyFlag) {
           // warn! oms data format require List<Map<String,String>> and the size of it must be one.
           List<Map<String, String>> dataList=Lists.newArrayListWithExpectedSize(1);
           }
          

          說明

          該方法創(chuàng)建了一個大小固定為1且類型為Map<String,String>的數(shù)組鏈表,這個用法比較奇怪,需要注釋說明原因

          總結

          代碼里偶爾出現(xiàn)一些非常hack的邏輯且修改會引起較高風險,這個時候需要加注釋重點說明

          注釋掉的代碼

          反例

           private Object buildParamMap(Object request) throws Exception {
           if (List.class.isAssignableFrom(request.getClass())) {
           List<Object> input=(List<Object>)request;
           List<Object> result=new ArrayList<Object>();
           for (Object obj : input) {
           result.add(buildParamMap(obj));
           }
           return result;
           }
           Map<String, Object> result=new LinkedHashMap<String, Object>();
           Field[] fields=FieldUtils.getAllFields(request.getClass());
           for (Field field : fields) {
           if (IGNORE_FIELD_LIST.contains(field.getName())) {
           continue;
           }
           String fieldAnnotationName=field.getAnnotation(ProxyParam.class) !=null ? field.getAnnotation(
           ProxyParam.class).paramName() : HttpParamUtil.convertParamName(field.getName());
           //Object paramValue=FieldUtils.readField(field, request, true);
           //if (paramValue==null) {
           // continue;
           //}
           //
           //if (BASIC_TYPE_LIST.contains(field.getGenericType().getTypeName())) {
           // result.put(fieldAnnotationName, String.valueOf(paramValue));
           //} else {
           // result.put(fieldAnnotationName, this.buildParamMap(paramValue));
           //}
           }
           return result;
           }
          

          說明

          常見套路,為了方便需要的時候重新復用廢棄代碼,直接注釋掉。

          正例

          同上,刪除注釋部分代碼

          總結

          不要在代碼保留任何注釋掉的代碼,版本管理軟件如Git可以做的事情不要放到代碼里

          循規(guī)蹈矩式注釋

          反例

          /**
           * 類EcsOperateLogDO.java的實現(xiàn)描述:TODO 類實現(xiàn)描述
           * 
           * @author xxx 2012-12-6 上午10:53:21
           */
          public class DemoDO implements Serializable {
           private static final long serialVersionUID=-3517141301031994021L;
           /**
           * 主鍵id
           */
           private Long id;
           /**
           * 用戶uid
           */
           private Long aliUid;
           /**
           * @return the id
           */
           public Long getId() {
           return id;
           }
           /**
           * @param id the id to set
           */
           public void setId(Long id) {
           this.id=id;
           }
           /**
           * @return the aliUid
           */
           public Long getAliUid() {
           return aliUid;
           }
           /**
           * @param aliUid the aliUid to set
           */
           public void setAliUid(Long aliUid) {
           this.aliUid=aliUid;
           }
          }
          

          說明

          分析上述代碼可以發(fā)現(xiàn)兩處注釋非常別扭和多余:

          • 類注釋使用了默認模版, 填充了無效信息
          • IDE為Getter及Setter方法生成了大量的無效注釋

          正例

          /**
           * Demo model.
           * @author xxx 2012-12-6 上午10:53:21
           */
          public class DemoDO implements Serializable {
           private static final long serialVersionUID=-3517141301031994021L;
           /**
           * 主鍵id
           */
           private Long id;
           /**
           * 用戶uid
           */
           private Long aliUid;
           public Long getId() {
           return id;
           }
           public void setId(Long id) {
           this.id=id;
           }
           public Long getAliUid() {
           return aliUid;
           }
           public void setAliUid(Long aliUid) {
           this.aliUid=aliUid;
           }
          }
          

          總結

          • 不要保留任何循規(guī)蹈矩式注釋,比如IDE自動生成的冗余注釋
          • 不要產生任何該類注釋,可以統(tǒng)一配置IDE達到該效果,推薦使用靈狐插件

          日志式注釋

          反例

           /** 支持xxx code by xxx 2015/10/11 */
           String countryCode=param.getCountyCode();
           if(StringUtils.isNotBlank(countryCode) && !"CN".equals(countryCode)){
           imageOrderParam.setCountyCode(param.getCountyCode());
           imageOrderParam.setCurrency(param.getCurrency());
           }
          

          說明

          修改已有代碼很多人會手動添加注釋說明修改日期,修改人及修改說明等信息,這些信息大多是冗余的

          正例

          代碼同上,刪除該注釋

          總結

          不要在代碼中加入代碼的著作信息,版本管理可以完成的事情不要做在代碼里

          “拐杖注釋”

          反例

           /**
           * update config map, if the config map is not exist, create it then put the specified key and value, then return it
           * @param key config key
           * @param value config value
           * @return config map
           */
           public Map<String, String> updateConfigWithSpecifiedKV(final String key, final String value) {
           if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
           return Maps.newHashMap();
           }
           
           Map<String, String> config=queryConfigMap();
           if (MapUtils.isEmpty(config)) {
           return new HashMap<String, String>() {{
           put(key, value);
           }};
           }
           config.put(key, value);
           return config;
           }
          

          說明

          示例代碼簡單實現(xiàn)了更新指定map k-v等功能,如果目標map不存在則使用指定k-v初始化一個map并返回,方法名為 updateConfigWithSpecifiedKV ,為了說明方法的完整意圖,注釋描述了方法的實現(xiàn)邏輯

          正例

           /**
           * create or update config map with specified k-v.
           *
           * @param value config value
           * @return config map
           */
           public Map<String, String> createOrUpdateConfigWithSpecifiedKV(final String key, final String value) {
           if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
           return Maps.newHashMap();
           }
           Map<String, String> config=queryConfigMap();
           if (MapUtils.isEmpty(config)) {
           return new HashMap<String, String>() {{
           put(key, value);
           }};
           }
           config.put(key, value);
           return config;
           }
          

          總結

          拋棄“拐杖注釋”,不要給不好的名字加注釋,一個好的名字比好的注釋更重要

          過度html化的注釋

          反例

          /**
           * used for indicate the field will be used as a http param, the http request methods include as follows:
           * <li>Get</li>
           * <li>Post</li>
           * <li>Connect</li>
           *
           * the proxy param will be parsed, see {@link ProxyParamBuilder}.
           *
           * @author yzq
           * @date 2017/12/08
           */
          @Target(ElementType.FIELD)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface ProxyParam {
           /**
           * the value indicate the proxy app name, such as houyi.
           *
           * @return proxy app name
           */
           String proxyApp() default "houyi";
           /**
           * proxy request mapping http param.
           *
           * @return http param
           */
           String paramName();
           /**
           * the value indicate if the param is required.
           *
           * @return if this param is required
           */
           boolean isRequired() default true;
          }
          

          說明

          類注釋使用了大量的html標簽用來描述,實際效果并沒有帶來收益反而增加閱讀難度

          正例

          /**
           * used for indicate the field will be used as a http param.
           *
           * @author yzq
           * @date 2017/12/08
           */
          @Target(ElementType.FIELD)
          @Retention(RetentionPolicy.RUNTIME)
          @Documented
          public @interface ProxyParam {
           /**
           * the value indicate the proxy app name, such as houyi.
           *
           * @return proxy app name
           */
           String proxyApp() default "houyi";
           /**
           * proxy request mapping http param.
           *
           * @return http param
           */
           String paramName();
           /**
           * the value indicate if the param is required.
           *
           * @return if this param is required
           */
           boolean isRequired() default true;
          }
          

          總結

          • 普通業(yè)務注釋謹慎使用html標簽,它不會給你帶來明顯收益,只會徒增閱讀難度
          • 如果是公共api且用于生成javadoc可以考慮加入必要的html標簽,比如鏈接,錨點等

          二、編程語言注釋實踐

          Java

          文件/類注釋規(guī)范

          目前IDE安裝 靈狐 后會自動配置IDE的file templates為如下格式:

          /**
           * @author ${USER}
           * @date ${YEAR}/${MONTH}/${DAY}
           */
          

          __強烈建議使用如上配置,統(tǒng)一、簡潔就是最好。__如果有特殊需要需要定制類注釋可以參考下圖:

          方法注釋

           /** 
           * xxx
           * 
           * @param 
           * @param 
           * @return 
           * @throws 
           */
          

          IDE提供了統(tǒng)一的方法注釋模版,無需手動配置,好的方法注釋應該包括以下內容:

          • 方法的描述,重點描述該方法用來做什么,有必要可以加一個輸入輸出的例子
          • 參數(shù)描述
          • 返回值描述
          • 異常描述

          舉個例子:

           /**
           * Converts a <code>byte[]</code> to a String using the specified character encoding.
           *
           * @param bytes
           * the byte array to read from
           * @param charsetName
           * the encoding to use, if null then use the platform default
           * @return a new String
           * @throws UnsupportedEncodingException
           * If the named charset is not supported
           * @throws NullPointerException
           * if the input is null
           * @deprecated use {@link StringUtils#toEncodedString(byte[], Charset)} instead of String constants in your code
           * @since 3.1
           */
           @Deprecated
           public static String toString(final byte[] bytes, final String charsetName) throws UnsupportedEncodingException {
           return charsetName !=null ? new String(bytes, charsetName) : new String(bytes, Charset.defaultCharset());
           }
          

          塊注釋與行注釋

          • 單行代碼注釋使用行注釋 //
          • 多行代碼注釋使用塊注釋 /* */

          Python

          文件注釋

          • 重點描述文件的作用及使用方式
          #!/usr/bin/python
          # -*- coding: UTF-8 -*-
          """
          bazaar script collection.
          init_resource_entry, used for init bazaar resource such as vpc, vsw, sg, proxy ecs and so on.
          user manual:
          1. modify ecs.conf config your key, secret, and region.
          2. run bazaar_tools.py script, this process will last a few minutes,then it will generate a init.sql file.
          3. use idb4 submit your ddl changes.
          """
          

          類注釋

           """
           ecs sdk client, used for xxx.
           Attributes:
           client:
           access_key: 
           access_secret:
           region:
           """
          
          • 類應該在其定義下有一個用于描述該類的文檔字符串
          • 類公共屬性應該加以描述

          函數(shù)注釋

          def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
           """Fetches rows from a Bigtable.
           Retrieves rows pertaining to the given keys from the Table instance
           represented by big_table. Silly things may happen if
           other_silly_variable is not None.
           Args:
           big_table: An open Bigtable Table instance.
           keys: A sequence of strings representing the key of each table row
           to fetch.
           other_silly_variable: Another optional variable, that has a much
           longer name than the other args, and which does nothing.
           Returns:
           A dict mapping keys to the corresponding table row data
           fetched. Each row is represented as a tuple of strings. For
           example:
           {'Serak': ('Rigel VII', 'Preparer'),
           'Zim': ('Irk', 'Invader'),
           'Lrrr': ('Omicron Persei 8', 'Emperor')}
           If a key from the keys argument is missing from the dictionary,
           then that row was not found in the table.
           Raises:
           IOError: An error occurred accessing the bigtable.Table object.
           """
           pass
          
          • Args:列出每個參數(shù)的名字, 并在名字后使用一個冒號和一個空格, 分隔對該參數(shù)的描述.如果描述太長超過了單行80字符,使用2或者4個空格的懸掛縮進(與文件其他部分保持一致). 描述應該包括所需的類型和含義. 如果一個函數(shù)接受*foo(可變長度參數(shù)列表)或者**bar (任意關鍵字參數(shù)), 應該詳細列出*foo和**bar.
          • Returns: 描述返回值的類型和語義. 如果函數(shù)返回None, 這一部分可以省略
          • Raises:列出與接口有關的所有異常

          多行注釋與行尾注釋

          # We use a weighted dictionary search to find out where i is in
          # the array. We extrapolate position based on the largest num
          # in the array and the array size and then do binary search to
          # get the exact number.
          if i & (i-1)==0: # true iff i is a power of 2
          
          • 復雜操作多行注釋描述
          • 比較晦澀的代碼使用行尾注釋

          Golang

          行注釋

          常用注釋風格

          包注釋

          /**/ 通常用于包注釋, 作為一個整體提供此包的對應信息,每個包都應該包含一個doc.go用于描述其信息。

          /*
           ecs OpenApi demo,use aliyun ecs sdk manage ecs, this package will provide you function list as follows:
           DescribeInstances, query your account ecs.
           CreateInstance, create a ecs vm with specified params.
           */
          package ecsproxy
          

          JavaScript

          常用/**/與//,用法基本同Java。

          Shell

          只支持 # ,每個文件都包含一個頂層注釋,用于闡述版權及概要信息。

          其它

          待完善

          小結

          本文先總結了注釋在編程中的最佳實踐場景并舉例進行了說明,然后就不同編程語言提供了一些注釋模版及規(guī)范相關的實踐tips。關于注釋我個人的認知是:注釋即代碼,注釋即文檔,寫好注釋一個工程師必備的素質之一,在整潔代碼前提下,更少的注釋是跟高的追求。關于注釋的實踐暫時寫到這里,后面會持續(xù)完善,也歡迎大家提供好的tips,文中代碼大多出自于日常業(yè)務項目,也有部分摘自開源庫,若有不妥之處敬請指正。

          任何編程語言中,代碼需要根據(jù)不同的條件在給定的輸入中做不同的決定和執(zhí)行相應的動作。

          例如,在一個游戲中,如果玩家生命點為0,游戲結束。在天氣應用中,如果在早上被查看,顯示一個日出圖片,如果是晚上,則顯示星星和月亮。在這篇文章中,我們將探索JavaScript中所謂的條件語句如何工作。

          如果你使用JavaScript工作,你將寫很多包含條件調用的代碼。條件調用可能初學很簡單,但是還有比寫一對對if/else更多的東西。這里有些編寫更好更清晰的條件代碼的有用提示。

          1. 數(shù)組方法 Array.includes
          2. 提前退出 / 提前返回
          3. 用對象字面量或Map替代Switch語句
          4. 默認參數(shù)和解構
          5. 用 Array.every & Array.some 匹配全部/部分內容
          6. 使用可選鏈和空值合并

          1. 數(shù)組方法 Array.includes

          使用 Array.includes 進行多條件選擇

          例如:

             function printAnimals(animal) {
             if (animal==='dog' || animal==='cat') {
                console.log(I have a ${animal});
              }
             }
          
             console.log(printAnimals('dog')); // I have a dog
          
          
          

          上面的代碼看起來很好因為我們只檢查了兩個動物。然而,我們不確定用戶輸入。如果我們要檢查任何其他動物呢?如果我們通過添加更多“或”語句來擴展,代碼將變得難以維護和不清晰。

          解決方案:

          我們可以通過使用 Array.includes 來重寫上面的條件

            function printAnimals(animal) {
             const animals=['dog', 'cat', 'hamster', 'turtle']; 
          
             if (animals.includes(animal)) {
               console.log(I have a ${animal});
             }
            }
          
            console.log(printAnimals('hamster')); // I have a hamster
          
          
          

          這里,我們創(chuàng)建來一個動物數(shù)組,所以條件語句可以和代碼的其余部分抽象分離出來。現(xiàn)在,如果我們想要檢查任何其他動物,我們只需要添加一個新的數(shù)組項。

          我們也能在這個函數(shù)作用域外部使用這個動物數(shù)組變量來在代碼中的其他任意地方重用它。這是一個編寫更清晰、易理解和維護的代碼的方法,不是嗎?

          2. 提前退出 / 提前返回

          這是一個精簡你的代碼的非常酷的技巧。我記得當我開始專業(yè)工作時,我在第一天學習使用提前退出來編寫條件。

          讓我們在之前的例子上添加更多的條件。用包含確定屬性的對象替代簡單字符串的動物。

          現(xiàn)在的需求是:

          • 如果沒有動物,拋出一個異常
          • 打印動物類型
          • 打印動物名字
          • 打印動物性別
          const printAnimalDetails=animal=> {
            let result; // declare a variable to store the final value
          
            // condition 1: check if animal has a value
            if (animal) {
          
              // condition 2: check if animal has a type property
              if (animal.type) {
          
                // condition 3: check if animal has a name property
                if (animal.name) {
          
                  // condition 4: check if animal has a gender property
                  if (animal.gender) {
                    result=${animal.name} is a ${animal.gender} ${animal.type};;
                  } else {
                    result="No animal gender";
                  }
                } else {
                  result="No animal name";
                }
              } else {
                result="No animal type";
              }
            } else {
              result="No animal";
            }
          
            return result;
          };
          
          console.log(printAnimalDetails()); // 'No animal'
          
          console.log(printAnimalDetails({ type: "dog", gender: "female" })); // 'No animal name'
          
          console.log(printAnimalDetails({ type: "dog", name: "Lucy" })); // 'No animal gender'
          
          console.log(
            printAnimalDetails({ type: "dog", name: "Lucy", gender: "female" })
          ); // 'Lucy is a female dog'
          
          
          
          

          你覺得上面的代碼怎么樣?

          它工作得很好,但是代碼很長并且維護困難。如果不使用lint工具,找出閉合花括號在哪都會浪費很多時間。 想象如果代碼有更復雜的邏輯會怎么樣?大量的if..else語句。

          我們能用三元運算符、&&條件等語法重構上面的功能,但讓我們用多個返回語句編寫更清晰的代碼。

          const printAnimalDetails=({type, name, gender }={})=> {
            if(!type) return 'No animal type';
            if(!name) return 'No animal name';
            if(!gender) return 'No animal gender';
          
          // Now in this line of code, we're sure that we have an animal with all //the three properties here.
          
            return ${name} is a ${gender} ${type};
          }
          
          console.log(printAnimalDetails()); // 'No animal type'
          
          console.log(printAnimalDetails({ type: dog })); // 'No animal name'
          
          console.log(printAnimalDetails({ type: dog, gender: female })); // 'No animal name'
          
          console.log(printAnimalDetails({ type: dog, name: 'Lucy', gender: 'female' })); // 'Lucy is a female dog'
          
          
          

          在這個重構過的版本中,也包含了解構和默認參數(shù)。默認參數(shù)確保如果我們傳遞undefined作為一個方法的參數(shù),我們仍然有值可以解構,在這里它是一個空對象{}。

          通常,在專業(yè)領域,代碼被寫在這兩種方法之間。

          另一個例子:

            function printVegetablesWithQuantity(vegetable, quantity) {
            const vegetables=['potato', 'cabbage', 'cauliflower', 'asparagus'];
          
            // condition 1: vegetable should be present
             if (vegetable) {
               // condition 2: must be one of the item from the list
               if (vegetables.includes(vegetable)) {
                 console.log(I like ${vegetable});
          
                 // condition 3: must be large quantity
                 if (quantity >=10) {
                   console.log('I have bought a large quantity');
                 }
               }
             } else {
               throw new Error('No vegetable from the list!');
             }
           }
          
           printVegetablesWithQuantity(null); //  No vegetable from the list!
           printVegetablesWithQuantity('cabbage'); // I like cabbage
           printVegetablesWithQuantity('cabbage', 20); 
           // 'I like cabbage
           // 'I have bought a large quantity'
          
          
          

          現(xiàn)在,我們有:

          • 1 if/else 語句過濾非法條件
          • 3 級嵌套if語句 (條件 1, 2, & 3)

          一個普遍遵循的規(guī)則是:在非法條件匹配時提前退出。

            function printVegetablesWithQuantity(vegetable, quantity) {
          
            const vegetables=['potato', 'cabbage', 'cauliflower', 'asparagus'];
          
             // condition 1: throw error early
             if (!vegetable) throw new Error('No vegetable from the list!');
          
             // condition 2: must be in the list
             if (vegetables.includes(vegetable)) {
                console.log(I like ${vegetable});
          
               // condition 3: must be a large quantity
                if (quantity >=10) {
                  console.log('I have bought a large quantity');
                }
             }
           }
          
          
          

          通過這么做,我們少了一個嵌套層級。當你有一個長的if語句時,這種代碼風格特別好。

          我們能通過條件倒置和提前返回,進一步減少嵌套的if語句。查看下面的條件2,觀察我們是怎么做的:

            function printVegetablesWithQuantity(vegetable, quantity) {
          
            const vegetables=['potato', 'cabbage', 'cauliflower', 'asparagus'];
          
             if (!vegetable) throw new Error('No vegetable from the list!'); 
             // condition 1: throw error early
          
             if (!vegetables.includes(vegetable)) return; 
             // condition 2: return from the function is the vegetable is not in 
            //  the list 
          
          
            console.log(I like ${vegetable});
          
            // condition 3: must be a large quantity
            if (quantity >=10) {
                console.log('I have bought a large quantity');
            }
           }
          
          
          

          通過倒置條件2,代碼沒有嵌套語句了。這種技術在我們有很多條件并且當任何特定條件不匹配時,我們想停止進一步處理的時候特別有用。

          所以,總是關注更少的嵌套和提前返回,但也不要過度地使用。

          3. 用對象字面量或Map替代Switch語句

          讓我們來看看下面的例子,我們想要基于顏色打印水果:

          function printFruits(color) {
            // use switch case to find fruits by color
            switch (color) {
              case 'red':
                return ['apple', 'strawberry'];
              case 'yellow':
                return ['banana', 'pineapple'];
              case 'purple':
                return ['grape', 'plum'];
              default:
                return [];
            }
          }
          
          printFruits(null); // []
          printFruits('yellow'); // ['banana', 'pineapple']
          
          
          

          上面的代碼沒有錯誤,但是它仍然有些冗長。相同的功能能用對象字面量以更清晰的語法實現(xiàn):

          // use object literal to find fruits by color
            const fruitColor={
              red: ['apple', 'strawberry'],
              yellow: ['banana', 'pineapple'],
              purple: ['grape', 'plum']
            };
          
          function printFruits(color) {
            return fruitColor[color] || [];
          }
          
          
          

          另外,你也能用Map來實現(xiàn)相同的功能:

          // use Map to find fruits by color
            const fruitColor=new Map()
              .set('red', ['apple', 'strawberry'])
              .set('yellow', ['banana', 'pineapple'])
              .set('purple', ['grape', 'plum']);
          
          function printFruits(color) {
            return fruitColor.get(color) || [];
          }
          
          
          

          Map 允許保存鍵值對,是自從ES2015以來可以使用的對象類型。

          對于上面的例子,相同的功能也能用數(shù)組方法 Array.filte 來實現(xiàn)。

           const fruits=[
              { name: 'apple', color: 'red' }, 
              { name: 'strawberry', color: 'red' }, 
              { name: 'banana', color: 'yellow' }, 
              { name: 'pineapple', color: 'yellow' }, 
              { name: 'grape', color: 'purple' }, 
              { name: 'plum', color: 'purple' }
          ];
          
          function printFruits(color) {
            return fruits.filter(fruit=> fruit.color===color);
          }
          
          
          

          4. 默認參數(shù)和解構

          當使用 JavaScript 工作時,我們總是需要檢查 null/undefined 值并賦默認值,否則可能編譯失敗。

            function printVegetablesWithQuantity(vegetable, quantity=1) { 
          // if quantity has no value, assign 1
          
            if (!vegetable) return;
              console.log(We have ${quantity} ${vegetable}!);
            }
          
            //results
            printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!
            printVegetablesWithQuantity('potato', 2); // We have 2 potato!
          
          
          

          如果 vegetable 是一個對象呢?我們能賦一個默認參數(shù)嗎?

            function printVegetableName(vegetable) { 
              if (vegetable && vegetable.name) {
               console.log (vegetable.name);
             } else {
              console.log('unknown');
             }
           }
          
           printVegetableName(undefined); // unknown
           printVegetableName({}); // unknown
           printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage
          
          
          

          在上面的例子中,如果vegetable 存在,我們想要打印 vegetable name, 否則打印"unknown"。

          我們能通過使用默認參數(shù)和解構來避免條件語句 if (vegetable && vegetable.name) {} 。

            // destructing - get name property only
            // assign default empty object {}
          
            function printVegetableName({name}={}) {
             console.log (name || 'unknown');
           }
          
          
           printVegetableName(undefined); // unknown
           printVegetableName({ }); // unknown
           printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage
          
          
          

          因為我們只需要 name 屬性,所以我們可以使用 { name } 解構參數(shù),然后我們就能在代碼中使用 name 作為變量,而不是 vegetable.name。

          我們還賦了一個空對象 {} 作為默認值,因為當執(zhí)行 printVegetableName(undefined) 時會得到一個錯誤:不能從 undefined 或 null 解構屬性 name,因為在 undefined 中沒有name屬性。

          5. 用 Array.every & Array.some 匹配全部/部分內容

          我們能使用數(shù)組方法減少代碼行。查看下面的代碼,我們想要檢查是否所有的水果都是紅色的:

          const fruits=[
              { name: 'apple', color: 'red' },
              { name: 'banana', color: 'yellow' },
              { name: 'grape', color: 'purple' }
            ];
          
          function test() {
            let isAllRed=true;
          
            // condition: all fruits must be red
            for (let f of fruits) {
              if (!isAllRed) break;
              isAllRed=(f.color=='red');
            }
          
            console.log(isAllRed); // false
          }
          
          
          

          這代碼太長了!我們能用 Array.every 來減少代碼行數(shù):

          const fruits=[
              { name: 'apple', color: 'red' },
              { name: 'banana', color: 'yellow' },
              { name: 'grape', color: 'purple' }
            ];
          
          function test() {
            // condition: short way, all fruits must be red
            const isAllRed=fruits.every(f=> f.color=='red');
          
            console.log(isAllRed); // false
          }
          
          
          

          相似地,如果我們想測試是否有任何紅色的水果,我們能用一行 Array.some 來實現(xiàn)它。

          const fruits=[
              { name: 'apple', color: 'red' },
              { name: 'banana', color: 'yellow' },
              { name: 'grape', color: 'purple' }
          ];
          
          function test() {
            // condition: if any fruit is red
            const isAnyRed=fruits.some(f=> f.color=='red');
          
            console.log(isAnyRed); // true
          }
          
          
          
          

          6. 使用可選鏈和空值合并

          這有兩個為編寫更清晰的條件語句而即將成為 JavaScript 增強的功能。當寫這篇文章時,它們還沒有被完全支持,你需要使用 Babel 來編譯。

          可選鏈允許我們沒有明確檢查中間節(jié)點是否存在地處理 tree-like 結構,空值合并和可選鏈組合起來工作得很好,以確保為不存在的值賦一個默認值。

          這有一個例子:

             const car={
              model: 'Fiesta',
              manufacturer: {
              name: 'Ford',
              address: {
                street: 'Some Street Name',
                number: '5555',
                state: 'USA'
                }
              }
            } 
          
            // to get the car model
            const model=car && car.model || 'default model';
          
            // to get the manufacturer street
            const street=car && car.manufacturer && car.manufacturer.address && 
            car.manufacturer.address.street || 'default street';
          
            // request an un-existing property
            const phoneNumber=car && car.manufacturer && car.manufacturer.address 
            && car.manufacturer.phoneNumber;
          
            console.log(model) // 'Fiesta'
            console.log(street) // 'Some Street Name'
            console.log(phoneNumber) // undefined
          
          
          

          所以,如果我們想要打印是否車輛生產商來自美國,代碼將看起來像這樣:

           const isManufacturerFromUSA=()=> {
             if(car && car.manufacturer && car.manufacturer.address && 
           car.manufacturer.address.state==='USA') {
               console.log('true');
             }
           }
          
          
           checkCarManufacturerState() // 'true'
          
          
          

          你能清晰地看到當有一個更復雜的對象結構時,這能變得多亂。有一些第三方的庫有它們自己的函數(shù),像 lodash 或 idx。例如 lodash 有 _.get 方法。然而,JavaScript 語言本身被引入這個特性是非常酷的。

          這展示了這些新特性如何工作:

           // to get the car model
           const model=car?.model ?? 'default model';
          
           // to get the manufacturer street
           const street=car?.manufacturer?.address?.street ?? 'default street';
          
           // to check if the car manufacturer is from the USA
           const isManufacturerFromUSA=()=> {
             if(car?.manufacturer?.address?.state==='USA') {
               console.log('true');
             }
           }
          
          
          

          這看起來很美觀并容易維護。它已經(jīng)到 TC39 stage 3 階段,讓我們等待它獲得批準,然后我們就能無處不在地看到這難以置信的語法的使用。


          總結

          讓我們?yōu)榱司帉懜逦⒁拙S護的代碼,學習并嘗試新的技巧和技術,因為在幾個月后,長長的條件看起來像搬石頭砸自己的腳。


          主站蜘蛛池模板: 在线成人一区二区| 日本一区二区不卡视频| 在线视频精品一区| 亚洲av乱码一区二区三区香蕉 | 亚洲国产精品一区二区第一页 | 在线一区二区三区| 国内精品视频一区二区三区八戒| 中文无码一区二区不卡αv| 久久精品一区二区三区AV| 91香蕉福利一区二区三区| 99精品一区二区三区无码吞精 | 无码人妻精品一区二区三区99不卡| 国产精品盗摄一区二区在线| 国产免费av一区二区三区| 免费人人潮人人爽一区二区| 一区 二区 三区 中文字幕 | 国产日韩高清一区二区三区| 国产在线精品一区二区三区不卡| 国产综合精品一区二区三区| 无码国产精品一区二区免费式芒果| 精品无码人妻一区二区三区品| 国产福利一区二区三区在线视频| 午夜AV内射一区二区三区红桃视| 无码人妻精一区二区三区 | 无码少妇一区二区浪潮av| 国产午夜毛片一区二区三区| 亚洲av鲁丝一区二区三区| 中文精品一区二区三区四区| 精品国产日韩一区三区| 无码精品久久一区二区三区 | 国产成人精品第一区二区| 在线一区二区三区| 内射女校花一区二区三区| 亚洲福利一区二区三区| 日本一区二区三区久久| 日产一区日产2区| 国产精品亚洲一区二区麻豆| 乱人伦一区二区三区| 亚洲不卡av不卡一区二区| 亚洲AV无码一区二区三区鸳鸯影院| 一区二区三区在线免费观看视频|