linux cpu占用率如何看
284
2022-09-27
代码整洁之道(理论篇)
目录
整洁代码重要性
有意义的命名
函数
注释
格式
对象和数据结构
错误处理
边界
单元测试
类
系统
迭进
总结
推荐一本书:罗伯特 C. 马丁的《代码整洁之道》。
组内最近在强调研发意识,即对线上有一颗敬畏之心:
营地意识:让代码比你来的时候更干净,警惕破窗效应;信息同步:变更同步关键角色,相信群众力量;风险意识:提高风险评估意识,凡事留个后路;刨根问题:问题追查到底,而非点到为止;自食其力:切勿依赖测试同学兜底。
本文旨在讨论营地意识,此篇为理论篇,分别从命名、函数、注释、格式、对象和数据结构、错误处理、边界、单元测试、类、系统等多个方面进行阐述。
整洁代码重要性
1. 糟糕代码会毁了公司,代码整洁不仅关乎效率,还关乎生存;赶上DDL/做得快的唯一方法是保持代码整洁。
2. 假如你是位医生,病人请求做手术前别洗手,因为会花费太多时间,医生会选择拒绝遵从,因为了解疾病和感染的风险。同理,程序员遵从不了解混乱风险的产品经理的意愿,是不专业的。
3. 什么是整洁的代码?Bjarne说整洁的代码只做好一件事情,完善的错误处理;Dave说易修改、“尽量少”以及字面上表达其含义;Ron说不要重复代码,只做一件事。
有意义的命名
变量、函数、参数、类和封包均需命名。
1. 名副其实:体现本意的名称更容易理解和修改;使用读得出来的名称;使用可搜索的名称:长名胜于短名。
优化前:
//读者很容易有以下问题://1.list1是什么类型的东西?//2.list[0]是什么意义?//3.4的意义是什么?//4.怎么使用返回的列表?public List
优化后:
//1.盘名为gameBoard,而非theList的单元格列表;//2.不用int数组表示单元格,而是另一个类;//3.类中包含一个名副其实的函数isFlagged(),掩盖魔术数。public List
2. 避免误导,别用双关语:1.避免留下掩藏代码本意的错误线索。eg:推荐accountGroup;不推荐hp(专有名称)、accountsList(如果非List类型,会误导);2.提防外形相似度高的名称。
3. 做有意义的区分:1.不推荐数字系列命名;2.废话是没有意义区分,ProductData和ProductInfo无区别,variable不应该出现在变量中,table永远别出现在表名中。
4. 避免使用编码:1.成员前缀:作者不推荐,但我觉得成员变量m打头、静态变量s打头,普通变量无打头更具标示性。2.接口与实现:接口用IShapFactory,实现用ShapeFactoryImpl。
5. 类名和方法名:类名是名词或名词短语(eg:Customer、WikiPage、Account等);方法名是动词或动词短语(eg:postPayment、deletePage等)。
6. 添加有语义的语境:几个变量能组合成类的可以抽象为类。
函数
1. 短小:20行封顶最佳,每个函数依序把你带到下一个函数。
2. 只做一件事:应该做一件事,只做一件事,做好一件事;无副作用:函数承诺只做一件事。要么做什么事,要么回答什么事,二者不可兼得。
3. 抽象层级:函数中的语句要在同一个抽象级上。区分较高、中间、较低抽象层级。
4. switch语句:1.switch语句处于较低抽象层级且永不重复;2.可以创建多态对象以尽可能避免switch语句。
5. 命名方式:1.长名称;2.花时间打磨;3.命名方式一致。
6. 函数参数:最理想的是0参数,避免3参数以上。1.单参数的普遍形式:询问该参数的问题(boolean isFileExists(XXX));将该参数操作并转换(InputStream fileOpen(XXX));事件(void passwordAttempt(XXX))。2.双参数的普遍形式:尽可能利用一些机制转换为单参数。比如构建新类、某参数变为成员变量等。3.三参数:排序、琢磨、忽略都很重要,如果函数有很多参数,应该要封装成类了。
7. 抽离try...catch代码块。
public void delete(Page page) { try { deletePageAndAllReferences(page); } catch (Exception e) { logError(e); }}private void deletePageAndAllReferences(Page page) throws Exception { deletePage(page); registry.deleteReference(page.name); configKeys.deleteKey(page.name.makeKey());}private void logError(Exception e) { logger.log(e.getMessage());}
8. 别写重复代码,先写代码再打磨。
重构前:
public static String testableHtml( PageData pageData, boolean includeSuiteSetup) throws Exception { WikiPage wikiPage = pageData.getWikiPage(); StringBuffer buffer = new StringBuffer(); if (pageData.hasAttribute("Test")) { if (includeSuiteSetup) { WikiPage suiteSetup = PageCrawlerImpl.getInheritedPage( SuiteResponder.SUITE_SETUP_NAME, wikiPage ); if (suiteSetup != null) { WikiPagePath pagePath = suiteSetup.getPageCrawler().getFullPath(suiteSetup); String pagePathName = PathParser.render(pagePath); buffer.append("!include -setup .") .append(pagePathName) .append("\n"); } } WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage); if (setup != null) { WikiPagePath setupPath = wikiPage.getPageCrawler().getFullPath(setup); String setupPathName = PathParser.render(setupPath); buffer.append("!include -setup .") .append(setupPathName) .append("\n"); } } //..... pageData.setContent(buffer.toString()); return pageData.getHtml();}
重构一次:
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { boolean isTestPage = pageData.hasAttribute("Test"); if (isTestPage) { WikiPage testPage = pageData.getWikiPage(); StringBuffer newPageContent = new StringBuffer(); includeSetupPages(testPage, newPageContent, isSuite); newPageContent.append(pageData.getContent()); includeTeardownPages(testPage, newPageContent, isSuite); pageData.setContent(newPageContent.toString()); } return pageData.getHtml();}
最终重构:
public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { if (isTestPage(pageData)) includeSetupAndTeardownPages(pageData, isSuite); return pageData.getHtml();}
注释
别给糟糕的代码加注释-重新写吧。
1. 原则:花心思减少注释,持续维护注释,因为不准确的注释远比没注释糟糕得多;用代码去阐释注释;注释可附上文档链接;
//重构前:// Check to see if the employee is eligible for full benefits if ((employee.flags & HOURLY_FLAG) && (employee.age > 65)) //重构后:if (employee.isEligibleForFullBenefits())
2. 注释的作用是:提供信息、对意图的解释、阐释(正确性)、警示、TODO注释、放大不合理之物重要性。
// 提供信息// format matched kk:mm:ss EEE, MMM dd, yyyyPattern timeMatcher = Pattern.compile( "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*"); //对意图解释public int compareTo(Object o){ //XXXXX return 1; // we are greater because we are the right type.}//阐释assertTrue(a.compareTo(b) != 0); // a != b//警示// Don't run unless you// have some time to kill. public void _testWithReallyBigFile(){ //XXXXX}//TODO注释//TODO-MdM these are not needed// We expect this to go away when we do the checkout modelprotected VersionInfo makeVersion() throws Exception{ return null;}//放大不合理之物的重要性String listItemContent = match.group(3).trim();// the trim is real important. It removes the starting // spaces that could cause the item to be recognized// as another list.new ListItemWidget(this, listItemContent, this.level + 1);return buildList(text.substring(match.end()));
3. 警惕:多余和误导性的注释、循规式注释、日志式注释、废话注释、不明显注释等
//多余和误导性的注释// Utility method that returns when this.closed is true. Throws an exception// if the timeout is reached.public synchronized void waitForClose(final long timeoutMillis) throws Exception{ if(!closed) { wait(timeoutMillis); if(!closed) throw new Exception("MockResponseSender could not be closed"); }}//循规式注释/** * * @param title The title of the CD * @param author The author of the CD * @param tracks The number of tracks on the CD * @param durationInMinutes The duration of the CD in minutes */public void addCD(String title, String author, int tracks, int durationInMinutes) { CD cd = new CD(); cd.title = title; //XXX}//废话注释/** The day of the month. */ private int dayOfMonth; //能用代码别用注释//重构前// does the module from the global list
格式
垂直格式:
1. 最多200~500行,向报纸学习。
横向格式:
1. 一行不超过120个字符,一屏。
2. 水平方向上的区隔与靠近:空格字符将相关性较弱的事物区隔开。赋值操作符周围、参数一一隔开等。
代码示例:
public class WikiPageResponder implements SecureResponder { protected WikiPage page; protected PageData pageData; protected String pageTitle; protected Request request; protected PageCrawler crawler; public Response makeResponse(FitNesseContext context, Request request) throws Exception { String pageName = getPageNameOrDefault(request, "FrontPage"); loadPage(pageName, context); if (page == null) return notFoundResponse(context, request); else return makePageResponse(context); } private String getPageNameOrDefault(Request request, String defaultPageName) { String pageName = request.getResource(); if (StringUtil.isBlank(pageName)) pageName = defaultPageName; return pageName; } protected void loadPage(String resource, FitNesseContext context) throws Exception { WikiPagePath path = PathParser.parse(resource); crawler = context.root.getPageCrawler(); crawler.setDeadEndStrategy(new VirtualEnabledPageCrawler()); page = crawler.getPage(context.root, path); if (page != null) pageData = page.getData(); } private Response notFoundResponse(FitNesseContext context, Request request) throws Exception { return new NotFoundResponder().makeResponse(context, request); } private SimpleResponse makePageResponse(FitNesseContext context) throws Exception { pageTitle = PathParser.render(crawler.getFullPath(page)); String html = makeHtml(context); SimpleResponse response = new SimpleResponse(); response.setMaxAge(0); response.setContent(html); return response; }...
对象和数据结构
1. 数据抽象:隐藏实现关乎抽象,类并不是简单地用取值器和赋值器将其变量推向外界,而是暴露抽象接口,以便用户无需了解数据的实现操作数据本体。
2. 数据、对象的反对称性:过程式代码难以添加新数据结构,因为必须修改所有函数;面向对象代码难以添加新函数,因为需要修改所有类。
//过程式形状代码public class Square { public Point topLeft; public double side;}public class Rectangle { public Point topLeft; public double height; public double width;}public class Circle { public Point center; public double radius;}public class Geometry { public final double PI = 3.141592653589793; public double area(Object shape) throws NoSuchShapeException { if (shape instanceof Square) { Square s = (Square)shape; return s.side * s.side; } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle)shape; return r.height * r.width; } else if (shape instanceof Circle) { Circle c = (Circle)shape; return PI * c.radius * c.radius; } throw new NoSuchShapeException(); }}
//多态式形状代码public class Square implements Shape { private Point topLeft; private double side; public double area() { return side*side; }}public class Rectangle implements Shape { private Point topLeft; private double height; private double width; public double area() { return height * width; }}public class Circle implements Shape { private Point center; private double radius; public final double PI = 3.141592653589793; public double area() { return PI * radius * radius; }}
3. 得墨忒定律:模块不应该了解它所操作对象的内部细节,链式调用可能会需要各种判空逻辑。
//重构前final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();//重构后//我们发现,取得临时目录绝对路径的初衷是为了创建制定名称的临时文件。BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
错误处理
1. 使用异常返回而非返回码
//重构前public class DeviceController { ... public void sendShutDown() { DeviceHandle handle = getHandle(DEV1); // Check the state of the device if (handle != DeviceHandle.INVALID) { // Save the device status to the record field retrieveDeviceRecord(handle); // If not suspended, shut down if (record.getStatus() != DEVICE_SUSPENDED) { pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } else { logger.log("Device suspended. Unable to shut down"); } } else { logger.log("Invalid handle for: " + DEV1.toString()); } } } //重构后 //采用异常处理 public class DeviceController { ... public void sendShutDown() { try { tryToShutDown(); } catch (DeviceShutDownError e) { logger.log(e); } } private void tryToShutDown() throws DeviceShutDownError { DeviceHandle handle = getHandle(DEV1); DeviceRecord record = retrieveDeviceRecord(handle); pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } private DeviceHandle getHandle(DeviceID id) { ... throw new DeviceShutDownError("Invalid handle for: " + id.toString()); ... } ...}
2. 缩小异常的具体范围。比如将Exception修改为FileNotFoundException
3. 特例模式:创建一个类或配置一个对象,用于处理特例。
//重构前:try { MealExpenses expenses = expenseReportDAO.getMeals(employee.getID()); m_total += expenses.getTotal();} catch(MealExpensesNotFound e) { m_total += getMealPerDiem();}//重构后MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());m_total += expenses.getTotal();//异常行为被封装至特例对象中public class PerDiemMealExpenses implements MealExpenses { public int getTotal() { // return the per diem default }}
4. 别返回null值、别传递null值。
//重构前:List
边界
使用第三方SDK应对其提供的API做好封装。
单元测试
1. 整洁的测试:测试显然呈现构造-操作-检验模式;分三个环节:构造测试数据、操作测试数据、检验操作是否得到期望的结果。
2. FIRST原则:
快速(Fast):测试应当足够快;独立(Independent):测试应当相互独立,每次只测试一个概念;可重复(Repeatable):测试应当在任何环境中重复通过;自足验证(Self-Validating):测试应该有布尔值输出,无需查看日志文件;及时(Timely):测试应该及时编写。
类
1. 类的组织:
(1)先公共静态变量,后私有静态变量,私有实体变量;
(2)自顶向下:公共函数应在变量列表之后,被调用的私有工具函数跟在公共函数后面;
(3)做好封装,放开封装是下策。
2. 类应当短小:对于函数,是代码行,对于类,是权责。
(1)单一权责原则:系统应该由许多短小的类而不是少数巨大的类组成,每个小类封装了一个权责,只有一个修改的理由,并与其他少数类一起协同达成期望的系统行为。
(2)内聚:一般来说,创造极大化内聚类是不可取也不可能的,另一方面,我们希望内聚性保持较高未知,内聚性高,意味着类中的方法和变量互相依赖、互相组合成一个逻辑整体。
//一个内聚类public class Stack { private int topOfStack = 0; List
(3)保持内聚性就会得到许多短小的类。以文中一段代码(P136)为例:PrimePrinter类中只有主程序,职责是处理执行环境,如果调用方式有变,它也会变化;RowColumnPagePrinter类是将数字列表格式化到固定行列的页面,若输出格式有变化,它也会变化。PrimeGenrator类懂得如何生成素数列表,如果计算素数算法发生改动,则类会改动。
3. 类应当对扩展开放,对修改封闭。
系统
系统应该是整洁的:将构造和使用分开;工厂模式;依赖注入等。后续会在《大话设计模式》具体讨论。
迭进
1. 四条原则:运行所有测试;不可重复;表达程序员的意图;尽可能减少类和方法数量。
2. 运行所有测试:测试用例越多,系统约会贴近低耦合、高内聚的目标
3. 消除重复:抽离方法;模板模式。
//重构前 public void scaleToOneDimension( float desiredDimension, float imageDimension) { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) return; float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f); RenderedOp newImage = ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor); image.dispose(); System.gc(); image = newImage; } public synchronized void rotate(int degrees) { RenderedOp newImage = ImageUtilities.getRotatedImage( image, degrees); image.dispose(); System.gc(); image = newImage;}//重构后public void scaleToOneDimension( float desiredDimension, float imageDimension) { if (Math.abs(desiredDimension - imageDimension) < errorThreshold) return; float scalingFactor = desiredDimension / imageDimension; scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f); replaceImage(ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor)); }public synchronized void rotate(int degrees) { replaceImage(ImageUtilities.getRotatedImage(image, degrees)); }private void replaceImage(RenderedOp newImage) { image.dispose(); System.gc(); image = newImage;}
//重构前public class VacationPolicy { public void accrueUSDivisionVacation() { // code to calculate vacation based on hours worked to date // ... // code to ensure vacation meets US minimums // ... // code to apply vaction to payroll record // ... } public void accrueEUDivisionVacation() { // code to calculate vacation based on hours worked to date // ... // code to ensure vacation meets EU minimums // ... // code to apply vaction to payroll record // ... } } //重构后 abstract public class VacationPolicy { public void accrueVacation() { calculateBaseVacationHours(); alterForLegalMinimums(); applyToPayroll(); } private void calculateBaseVacationHours() { /* ... */ }; abstract protected void alterForLegalMinimums(); private void applyToPayroll() { /* ... */ };}public class USVacationPolicy extends VacationPolicy { @Override protected void alterForLegalMinimums() { // US specific logic }}public class EUVacationPolicy extends VacationPolicy { @Override protected void alterForLegalMinimums() { // EU specific logic }}
3. 表达力:选择比较好的名称;保持函数和类的短小;多重构;编写好的单元测试。
4. 尽可能少的类和方法:优先级较低。
总结
重要的九条建议:
关于命名:类名和方法名:类名是名词或名词短语(eg:Customer、WikiPage、Account等);方法名是动词或动词短语(eg:postPayment、deletePage等)。添加有语义的语境:几个变量能组合成类的可以抽象为类。关于函数:短小精悍:20行封顶最佳,每个函数依序把你带到下一个函数。只做一件事:应该做一件事,只做一件事,做好一件事。无副作用:函数承诺只做一件事。要么做什么事,要么回答什么事,二者不可兼得。关于注释:花心思减少注释,不准确的注释远比没注释糟糕得多;用代码去阐释注释;注释可附上文档链接。关于格式:垂直方向最多200~500行,向报纸学习。垂直方向顺序:被调用的函数应该放在执行调用的函数下面,建立了自顶向下贯穿源代码模块的良好信息流。水平方向一行不超过120个字符。关于对象和数据结构:数据、对象的反对称性:过程式代码难以添加新数据结构,因为必须修改所有函数;面向对象代码难以添加新函数,因为需要修改所有类。关于错误处理:别返回null值、别传递null值。创建一个类或配置一个对象,用于处理特例。关于边界:FIRST原则:快速(Fast);独立(Independent);可重复(Repeatable);自足验证(Self-Validating);及时(Timely)。关于类:类应当短小:单一权责原则,内聚性高,保持内聚性就会得到许多短小的类。关于迭代:四条原则:运行所有测试;不可重复;表达程序员的意图;尽可能减少类和方法数量。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~