.對多個條件使用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.較少嵌套,早退
讓我們擴展前面的示例以包含另外兩個條件:
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
看看上面的代碼,我們有:
我個人遵循的一般規(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嵌套)。這是因為:
因此,始終旨在盡早減少筑巢和回歸,但不要過度。如果您感興趣,有一篇文章和StackOverflow討論會進一步討論這個主題:
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的示例:
// 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
注釋應該聲明代碼的高層次意圖,而非明顯的細節(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"); }
總結
在文件/類級別使用全局注釋來解釋所有部分如何工作
正例
/** * <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)
示例
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為例修改入口如下:
總結
適當添加警示注釋
正例
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)兩處注釋非常別扭和多余:
正例
/** * 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; } }
總結
日志式注釋
反例
/** 支持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; }
總結
Java
文件/類注釋規(guī)范
目前IDE安裝 靈狐 后會自動配置IDE的file templates為如下格式:
/** * @author ${USER} * @date ${YEAR}/${MONTH}/${DAY} */
__強烈建議使用如上配置,統(tǒng)一、簡潔就是最好。__如果有特殊需要需要定制類注釋可以參考下圖:
方法注釋
/** * xxx * * @param * @param * @return * @throws */
IDE提供了統(tǒng)一的方法注釋模版,無需手動配置,好的方法注釋應該包括以下內容:
舉個例子:
/** * 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
多行注釋與行尾注釋
# 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更多的東西。這里有些編寫更好更清晰的條件代碼的有用提示。
使用 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ù)組變量來在代碼中的其他任意地方重用它。這是一個編寫更清晰、易理解和維護的代碼的方法,不是嗎?
這是一個精簡你的代碼的非常酷的技巧。我記得當我開始專業(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)在,我們有:
一個普遍遵循的規(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,代碼沒有嵌套語句了。這種技術在我們有很多條件并且當任何特定條件不匹配時,我們想停止進一步處理的時候特別有用。
所以,總是關注更少的嵌套和提前返回,但也不要過度地使用。
讓我們來看看下面的例子,我們想要基于顏色打印水果:
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);
}
當使用 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屬性。
我們能使用數(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
}
這有兩個為編寫更清晰的條件語句而即將成為 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護的代碼,學習并嘗試新的技巧和技術,因為在幾個月后,長長的條件看起來像搬石頭砸自己的腳。
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。