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專案.这里按部就班的做了一遍,感觉 ...
随机推荐
- C++socket中select函数
文章参考 https://www.cnblogs.com/shmilxu/p/4835873.html
- Redis高级数据类型
## 1.Redis相关配置信息 服务器端设定 设置服务器以守护进程的方式运行 daemonize yes|no 绑定主机地址 (只能此ip访问) bind 127.0.0.1 设置服务器端口号 po ...
- centos 8远程分发复制jdk到另一个虚拟机
在localzly节点操作成功后可以使用远程复制命令将JDK远程复制到slave1节点之中:(此命令在localzly中操作) scp -r /usr/java root@slave1:/usr/ 配 ...
- MES集成警报灯系统,Http远程控制系统设计
核心功能设计 警报灯实机演示:https://www.bilibili.com/video/BV1294y1M7f3?p=2 接受服务器发送http·post请求远程控制警报灯,可接入MES等系统. ...
- [VueJsDev] 基础知识 - ES6循环使用手册
[VueJsDev] 目录列表 https://www.cnblogs.com/pengchenggang/p/17037320.html ES6循环使用手册 ::: details 目录 目录 ES ...
- win10 有 休眠 功能,将内存保存到文件,开机10秒左右,恢复之前idea等所有软件
休眠 休眠 休眠 重要的事情说三遍. 提示,默认不显示,需要控制面板 电源里面设置下.
- Handler源码解析
Handler源码解析 一.基本原理回顾 在android开发中,经常会在子线程中进行一些操作,当操作完毕后会通过handler发送一些数据给主线程,通知主线程做相应的操作. 探索其背后的原理:子线程 ...
- WPF 组件间通信 MVVM 进行解耦
假设有这样一个需求,有这样一个聊天界面,主界面是选项卡,其一选项卡内部是真正的聊天列表和聊天界面,我们需要实时的在主界面显示未读消息的数量 假设我们已经有方法可以拿到未读消息的数量,那么如何在主界面的 ...
- C++ Concurrency in Action 读书笔记三:并发操作的同步
Chapter 4 并发操作的同步·Synchronizing concurrent operations
- VS2010插件NuGet
下载地址 NuGet Package Manager - Visual Studio Marketplace NuGet包地址 NuGet Gallery | Home