Code Aesthetic
01 Abstraction
Abstraction is the process of aggregating code with high similarity among multiple classes into one class to achieve common goals.
The idea of "identifying repetition and extracting it out" will make us get into the mode of "code repetition bad and more abstraction good". But there's hidden trade-off that doesn't get considered, it is coupling.
Coupling is an equal and opposite reaction of abstraction. For every bit of abstraction you add, you have added more coupling.
There are two cases where it can be worth to use abstraction:
- Many implementations with worth complex construction
- Deferred execution from creation
It's good to only apply abstraction when the value it brings outweighs the coupling.
02 Naming things
There are only two hard things in computer science: cache invalidation and naming things.
(1) You shouldn't name variables with single letter
Because the single letter doesn't tell you anything about the variable.
int x;
It might because math and computer science were more or less the same.
The right way like this:
int number;
(2) Don't abbreviate names
int bw;
Abbreviations rely on context that you may or may not have. Abbreviations can make you spend more time reading code then writing code.
int boardWidth;
Abbreviations used to help because of two reasons:
- saved you typing
- Auto-Complete takes less keyboard strokes than ever to write variable names.
- screens were 80 characters wide
- We have massive 4K screen now.
So there is no real advantage to abbreviation.
(3) Don't put types in your name
This typing method named Hungarian notation. Hungarian notation is where you'd prefix the type to the variable name.
bool bIsValid;
int32_t iMark;
uint32_t uIndex;
char* szName;
This might because everything would basically be int before we had good standard types in C. The type of the variable wouldn't actually tell you what was inside of it.
int bIsValid;
int iMark;
int uIndex;
int szName;
But now with statically typed languages, the type should tell you exactly what you are looking at. So putting types in your variables is no longer necessary.
bool IsValid;
int32_t Mark;
uint32_t Index;
char* Name;
(4) Add units to variables unless the type tells you
It is good to put units in your variables names.
If you have a function that accepts a delay time, for example, and if the value of the formal parameter is in seconds, you should name the variable like this.
void execute(int delaySeconds) {}
But even better than that is to have a type that removes the ambiguity completely. In C#, there is a type called TimeSpan. And in C++, it can be chrono::duration.
void execute(TimeSpan delay) {}
The type abstracts the user from understanding the exact underlying unit.
For dynamically typed languages like Python, you cannot rely on type declarations to help.
def __init__(self, delaySeconds):
self.delay = delaySeconds
(5) Don't put types to your types
In C# there's this pattern of prefixing interfaces with "I".
interface IExecute {}
Don't naming a class with "Base" or "Abstract".
class Student extend BaseStudent {}
This isn't a great name because it doesn't help the users of the class.
If you ever find yourself unable to come up with a good name for the parent class, it probably means that we should actually rename the child class instead.
class NewStudent extend Student {}
Sometimes if you are struggling to name something, it's actually indicative that the code structure is to blame.
(6) Refactor if you find yourself naming code "Utils"
You don's see a bundle of utils in standard libraries. Because they can all be sorted into modules that have good names.
03 Never Nester
The Never Nester never nests their code.
Nesting code is when you add more inner block to a function. We consider each open brace to be adding one more depth to the function.
Here is a function which its depth get 4:
int calculate(int left, int right) {
if (left < right) {
int sum = 0;
for (int number = left; number <= right; number++) {
if (number % 2 == 0) {
sum += number;
}
}
return sum;
} else {
return 0;
}
}
There are two methods you can use to de-nest:
- Extraction: this is where you pull out part of the function into its own function.
- Inversion: this is simply flipping conditions and switching to an early return.
With these methods, we can change that function like this:
int filterNumber(int number) {
if (number % 2 == 0) {
return number;
}
return 0;
}
int calculate(int left, int right) {
if (left > right) {
return 0;
}
int sum = 0;
for (int number = left; number <= right; number++) {
sum += filterNumber(number);
}
return sum;
}
04 Prefer Composition Over Inheritance
Both composition and inheritance are trying to solve the same problem: You have a piece of code that you are trying to reuse.
class Person {
public string name { get; set; }
public int age { get; set; }
public bool isValid(string name, int age) {
return (this.name == name && this.age == age);
}
}
(1) Inheritance
Inheritance is when you have a class that contains functionality you want to reuse. So you create a subclass to extending its functionality.
class Student extend Person {}
If you simply extend a class, you have basically created a copy of the class with a new name, and then you can inject new methods to extend or override parts.
class Student extend Person {
public string school { get; set; }
public int grade { get; set; }
public bool isValid(string school, int grade) {
return (this.school == school && this.grade == grade);
}
}
But when you need to create multiple different subclasses, you will discover the downsides of inheritance that you've couple yourself to the parent class. The struct of parent is thrust upon the child. We are forced to implement certain functions that we don't need, in order to be able to reuse the functions that we do need, even thought they don't make sense for our subclass.
Inheritance breaks down when you need to change the code. Change is the enemy of perfect design and you often paint yourself into a corner early on with your inheritance design. This is because inheritance naturally asks you to bundle all common elements into a parent class. But as soon as you find an exception to the commonality, it requires big changes.
So our alternative is to use composition.
(2) Composition
Composition is a pattern that you are doing whenever you reuse the code without inheritance. If we have two classes and they want to reuse code, they simply use the code. With the help of composition, we do not need to use abstract class and no longer inherit the parent class. For methods that need to be overrode, we'll simply pass in the parent class in question instead of accessing them through this.
class Student {
public void execute(User user) {}
}
Now the user no longer chooses the one class that suits their needs. They also combine classes together for their particular use case.
(3) Abstracting with Inheritance
Inheritance is interesting because it actually combines two capabilities: the ability to reuse code and the ability to build an abstraction. Creating abstractions allow a piece of code to reuse another piece of code, but also not know which piece of code it is using.
But with composition, you don't have parent classes. You have just using the types you want. For these new classes without inheritance, we still want to be able to call the methods in "parent class" without caring about which class it is. It is time for interfaces to come in.
(4) Abstracting with Interfaces
Instead of a full parent class with all its variables and methods, an interface simply describes the contract of what an object can do.
interface StudentInterface {
void execute(User user);
}
Interfaces are minimal. Parent classes share everything by default, making them more difficult to change. But interfaces define only the critical parts of the contract and are easily tacked on to existing classes. There is a name for what we just did there: Dependency Injection (Passing in an interface for what you are going to use).
(5) When to use Inheritance
The cons of composition:
- Code Repetition in interface implementations to initialize internal types
- Wrapper Methods to expose information from internal types
The pros of composition:
- Reduces coupling to re-used code
- Adaptable as new requirements come in
Inheritance might be useful if you are working inside of an existing system that had highly repetitive code where you only needed to modify one thing. If you do use the Inheritance design the class to be inherited, you should avoid protected member variables with direct access. For overriding, create a protected API (Application Programming Interface) from child classes to use. And mark all other methods as final/sealed/private.
05 No Comments
Here is a piece of code.
if status == 2:
message.markSent()
That means the method of message which is called markSent will execute when the value of status is 2. Looking at that code, it is not obvious what 2 signals. We could add comment to explain. But even better we can create a constant representing the variable instead.
MESSAGE_SENT = 2
if status == MESSAGE_SENT:
message.markSent()
The if statement now reads like a comment.
If your code is complex enough that it warrants a comment, you should see if you can simplify or refactor the code to make it better instead.
(1) Types can also make comments redundant
In older C++, there's no built in memory management. You often have a function like this where you get back a thing.
message* receive_message();
But we need to make clear who will take ownership (the responsibility to release the memory after everyone is done with it) of the thing. That is rely on comments to explain the ownership. But since C++11, there has been added a new type called unique_ptr.
std::unique_ptr<message> receive_message();
The type represents a unique reference to the object and tells you explicitly that you now own it without the need for a comment.
But why not both write high-quality code and add comments?
(2) Comments get bugs like code
// 2 means message sent
if status == 2:
message.markSent()
When people make changes to code, they often do not update the comment to match.
// 2 means message sent
if status == 2:
message.markReceived()
But unlike comment, we have tools to stop bugs from entering code: tests, compiler checks and linting. Comments can lie, but code cannot.
For understanding the code, the better way than reading comments is reading Code Documentation.
(3) Code Documentation
Code Documentation describes the high level architecture and public APIs of a system. The difference between Code Documentation and Code Comments:
- Code Documentation: How code is used
- Code Comments: How code works
Tools like Doxygen, pydoc and JavaDoc generate documents directly from code files and therefore change alongside our code. In useful Code Documentation, you need to include the following key points:
- What a class or API represents
- Interface expectations
- Thread safety
- Possible states
- Error conditions
(4) Exceptions for Comments
- Non Obvious Performance Optimizations
- References to Math or Algorithms
06 Premature Optimization
There is a relationship:
Performance-->Velocity-->Adaptability-->Performance
The key with the triangle is that performance is a problem, but it's not usually the first problem. There are two camps in performance issues:
Macro Performance
There are system wide performance considerations.
Micro Performance
This is fine tuned performance.
Premature Optimization usually occurs for micro performance.
For example, in C++, ++i is faster than i++, but we rarely use ++i. Because it cannot bring significant performance improvements.
Before beginning the optimization, you should check if it has a real performance problem.
There're so many factors to performance that there's only one way to properly optimize:
Measure
Measuring is critical because it can show you what will make things faster can make things slower.
Try Something
You can help form a hypothesis of how to make things better by doing an analysis.
Data Structures
Profile
A profiler can tell you what are the hotspots of your code. It can point out functions that are the most expensive.
Think About Under The Hood
Think About Memory
Measure Again
Code Aesthetic的更多相关文章
- Code Complete阅读笔记(三)
2015-05-26 628 Code-Tuning Techniques ——Even though a particular technique generally represen ...
- Code Complete阅读笔记(二)
2015-03-06 328 Unusual Data Types ——You can carry this technique to extremes,putting all the ...
- Visual Studio Code 代理设置
Visual Studio Code (简称 VS Code)是由微软研发的一款免费.开源的跨平台文本(代码)编辑器,在十多年的编程经历中,我使用过非常多的的代码编辑器(包括 IDE),例如 Fron ...
- 我们是怎么做Code Review的
前几天看了<Code Review 程序员的寄望与哀伤>,想到我们团队开展Code Review也有2年了,结果还算比较满意,有些经验应该可以和大家一起分享.探讨.我们为什么要推行Code ...
- Code Review 程序员的寄望与哀伤
一个程序员,他写完了代码,在测试环境通过了测试,然后他把它发布到了线上生产环境,但很快就发现在生产环境上出了问题,有潜在的 bug. 事后分析,是生产环境的一些微妙差异,使得这种 bug 场景在线下测 ...
- 从Script到Code Blocks、Code Behind到MVC、MVP、MVVM
刚过去的周五(3-14)例行地主持了技术会议,主题正好是<UI层的设计模式——从Script.Code Behind到MVC.MVP.MVVM>,是前一天晚上才定的,中午花了半小时准备了下 ...
- 在Visual Studio Code中配置GO开发环境
一.GO语言安装 详情查看:GO语言下载.安装.配置 二.GoLang插件介绍 对于Visual Studio Code开发工具,有一款优秀的GoLang插件,它的主页为:https://github ...
- 代码的坏味道(14)——重复代码(Duplicate Code)
坏味道--重复代码(Duplicate Code) 重复代码堪称为代码坏味道之首.消除重复代码总是有利无害的. 特征 两个代码片段看上去几乎一样. 问题原因 重复代码通常发生在多个程序员同时在同一程序 ...
- http status code
属于转载 http status code:200:成功,服务器已成功处理了请求,通常这表示服务器提供了请求的网页 404:未找到,服务器未找到 201-206都表示服务器成功处理了请求的状态代码,说 ...
- Visual Studio Code——Angular2 Hello World 之 2.0
最近看到一篇用Visual Studio Code开发Angular2的文章,也是一篇入门教程,地址为:使用Visual Studio Code開發Angular 2專案.这里按部就班的做了一遍,感觉 ...
随机推荐
- 开源的 Sora 复现方案,成本降低近一半!
近日,开发 ChatGPT 的 OpenAI 公司又放出王炸 Sora,一个可以根据文本生成视频的 AI 模型. 上图就是 OpenAI 公布的 Sora 生成的视频片段,可以毫不夸张地说 Sora ...
- 18 Codeforces Round 853 (Div. 2)C. Serval and Toxel's Arrays(算贡献)
C. Serval and Toxel's Arrays 这种题目做多了应该很容易从贡献的角度去考虑了. 考虑当前版本对答案的贡献,首先这个版本和其他版本取交集至少会包含它本身所以直接先把\(i * ...
- 尚硅谷Java 宋红康2023版 - 学习笔记
尚硅谷Java 宋红康2023版 - 学习笔记 观看地址 https://www.bilibili.com/video/BV1PY411e7J6 60-IDEA开发工具-HelloWorld的编写与相 ...
- jenkins安装和基本使用
参考:https://zhuanlan.zhihu.com/p/56037782(安装) https://gitee.com/oschina/Gitee-Jenkins-Plugin/(使用) htt ...
- 百度 Linux 运维工程师面试真题
百度 Linux 运维工程师面试真题 百度面了好久了,两个月了,估计都快成馊面了,一跟面条在走边边一不小心掉进了大海,于是 就有了汤面_经历非技术总结就两句话,幸运的是在朋友的帮助下顺利通过笔试,还认 ...
- 【STM32 F4 HAL】记录一个比较玄学的pwm输出问题
事情是这样的: 最近在做平衡小车,硬件电路都搭好了,试着驱动了下有刷电机,发现两个都动不了,就以为是电路的问题,后面又重新检查了一遍,问题就进化成了只有一个电机在转. 因为之前看过一个学长的博客说可能 ...
- 基于Rust的Tile-Based游戏开发杂记(02)ggez绘图实操
尽管ggez提供了很多相关特性的[demo](ggez/examples at master · ggez/ggez (github.com))供运行查看,但笔者第一次使用的时候还是有很多疑惑不解.经 ...
- 什么是XR扩展现实,XR云串流平台有哪些
什么是云XR (AR/VR/MR/SR) 虚拟现实(VR),传统的实现方式是通过计算机模拟虚拟环境,从而给人一种环境沉浸感.与传统视频相比,VR带来了前所未有的沉浸式体验. 增强现实(AR)是一种无缝 ...
- 记录--前端使用a链接下载内容增加loading效果
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 问题描述:最近工作中出现一个需求,纯前端下载 Excel 数据,并且有的下载内容很多,这时需要给下载增加一个 loading 效果. 代码 ...
- vue项目,关闭eslint语法检测
vue.config.js文件中 module.exports = { lintOnSave:false //关闭语法检查 } 然后重启项目生效!