点赞
评论
收藏
分享
举报
编程是门手艺,做一名优秀的将军
发表于2020-10-31 12:53

浏览 1.8k

文章标签

编程是门手艺,NGINX社区的经验分享 一文提过,专业的程序员擅长整体设计和细节处理能力。本文探讨整体设计,尤其是模块化这个技能。

全能天才,Fabrice Bellard

FFmpeg,最强大的流媒体库
QEMU,硬件虚拟化的虚拟机
TCC,迷你CC编译器
QuickJS,100%支持JS语法的C引擎
等等,以上皆出自一人之手,法国天才。
去年QuickJS曾一度刷爆技术圈,NGINX社区的哥们第一时间推荐给我看,并以天才称他。
这软件开拓了我的视野。本文以它为引子探讨我认为非常重要的技能:如何组织代码。

NJS,实现语言引擎真难

私下问过Fabrice Bellard(给QJS提过patch)开发QJS的历程,答案令人惊叹,他只用了两年的业余时间。参与NJS这几年,才深知实现语言引擎有多复杂。
NJS从17年开始,现在差不多完成40%。但基础已经非常良好,后续功能开发会快速很多。而且常用功能都已经支持,其中我支持了模块化,箭头函数,等常用的。语言解析引入了LL(k)。
看似做了些不错的工作。然而跟QJS比,以台球打个比方。一个长距离很准的选手,90%的球都能打进,看似很厉害。但对一个发力非常厉害的人来说,他可能只需80%的准度,再加良好的走位,就能轻松一杆清台。
提QJS不是妄自菲薄,这样对比很不公平。QJS作者本身就是个JS专家,他都能用JS实现虚拟机。参与NJS的人员,包括Igor都不是真正的JS语法行家,JS的语法着实太庞大。我们平时开发过程中,有个社区外的JS行家对我们帮助非常大,简直就是JS活字典。因此在前期,只能靠着语法手册,然后实现,有些实现跟语法的本质有出入的话,又得重头再来。举个例子,早期实现的apply和call两个语法真是让人吃尽了苦头,这也是我最早参与的,因为修复它的bug,做了重构,然后发现社区的人非常接受这种重构的做法,有种碰到知音的感觉。

QuickJS,五万行代码一个文件的软件

我会解释这种做法是合理的。此时必须提出来,后面再详加解释。

模块化,最好的代码组织方式

我在参与NJS时,第一件事就是让它支持模块化编程。NJS刚出来时我就开始关注,后面挺长一段时间,用NJS写代码只能放在一个文件里,这对代码组织是极不友好的。先看下JS的模块化用法:
main.js

/* 自定义模块 */
import foo from 'foo.js';
foo.inc();

/* 内置模块 */
import crypto from 'crypto';
var h = crypto.createHash('md5');
var hash = h.update('AB').digest('hex');

foo.js

var state = {count:0}

function inc() {
state.count++;
}

function get() {
return state.count;
}

export default {inc, get}

支持模块化之后,变得非常好用。这个大功能也是NGINX作者Igor亲自帮review和调整的,收获良多。客观讲,JS语法比Lua实在好用太多,NJS目前已经非常稳定,只是功能没那么繁多,推荐轻量应用考虑用NJS,而且社区非常活跃,相信未来可期。
现在轻瞥一下QuickJS的源码。

JSContext *JS_NewContext(JSRuntime *rt)
{
JSContext *ctx;

ctx = JS_NewContextRaw(rt);
if (!ctx)
return NULL;

JS_AddIntrinsicBaseObjects(ctx);
JS_AddIntrinsicDate(ctx);
JS_AddIntrinsicEval(ctx);
JS_AddIntrinsicStringNormalize(ctx);
JS_AddIntrinsicRegExp(ctx);
JS_AddIntrinsicJSON(ctx);
JS_AddIntrinsicProxy(ctx);
JS_AddIntrinsicMapSet(ctx);
JS_AddIntrinsicTypedArrays(ctx);
JS_AddIntrinsicPromise(ctx);
return ctx;
}

void *JS_GetContextOpaque(JSContext *ctx)
{
return ctx->user_opaque;
}

void JS_SetContextOpaque(JSContext *ctx, void *opaque)
{
ctx->user_opaque = opaque;
}

所有源代码扔进一个文件里,我看过不少软件的源码,而且是比较完整的。NGINX, Unit, NJS, Lua等,以个人感观而言,QuickJS是最好的。初看有点凌乱,但细看的话(可能需要很熟悉JS语法),绝对的大师之作。
假如想删除某个语法功能,在QuickJS里可以连续的从某行一直删除到另一行,连续的一块。这在其它软件是不可能做到的,要么多个文件都要删除,要么在一个文件也要删除多个不同的地方。我认为这就是模块化的精髓:高内聚。
学过设计原则的同学想必都知道软件要高内聚,低耦合。我的理解是只要做到了高内聚,低耦合就是自然而然的事情。
举个例子,要实现nginx lua模块。有两个重要的功能:nginx模块相关函数,lua封装相关函数。
过度设计方式:

ngx_http_lua_module.c
/* nginx模块相关函数 */

ngx_http_lua_request.c
/* lua封装相关函数 */

合理方式

ngx_http_lua_module.c
/* nginx模块相关函数 */
/* lua封装相关函数 */

https://github.com/hongzhidao/nginx-lua-module/blob/master/src/ngx_http_lua_module.c
过度设计是一种很容易踩进去的陷井。
讨论1:
如果有更多的功能,比如http subrequest这种功能进来时怎么办?
建议还是放在同一个文件里,不要被代码行数影响。
讨论2:
又有更多的功能,比如http share memory这种功能进来时怎么办?
是可以考虑独立到另一个文件了,原则就是要找到一个信服的理由,新的功能能独立成一个高内聚的模块。有个特征是它往往会有专门的API,比如共享内存操作的get, set等。
换另一个角度看,一个文件的引入本身也是一种成本,而且比函数级别更高。每次的重构都应该带来实质的价值。这是我坚持尽量放同一个文件的原因。我早期提过几次建议,想对njs做类似的事情,后来证明有些是过度设计的。而有些是正确的,比如把njs_vm.c分成njs_vm.c和njs_vmcode.c。一个负责虚拟机,一个负责字节码处理。

总结一下:
高内聚是最高准则。
引入新文件成本高于函数,要有实质的价值才做。
不要被代码行数影响。
协作只是一种分工,不能做为破坏高内聚的理由。

再谈设计

前面说QuickJS的代码质量非常高,是因为他的设计令人折服。整个QJS的代码行数不到5万,实现了100%的语法,其中还包括非常硬核的大数和正则,都自己造轮子。从整个引擎的实现方面它就做了高度的抽象,而且用的算法非常简单有效。举个例子,JS里对象的属性操作应该是最常用的,比如 a[‘name’]。a和name在语法解析时都是字符串,术语叫token。QJS用一个非常高效的hash实现,将所有JS用到字符串的都包括进去了,代码也很少。

typedef struct JSShapeProperty {
uint32_t hash_next : 26; /* 0 if last in list */
uint32_t flags : 6; /* JS_PROP_XXX */
JSAtom atom; /* JS_ATOM_NULL = free property entry */
} JSShapeProperty;

struct JSShape {
uint32_t prop_hash_end[0]; /* hash table of size hash_mask + 1
before the start of the structure. */
JSGCObjectHeader header;
/* true if the shape is inserted in the shape hash table. If not,
JSShape.hash is not valid */
uint8_t is_hashed;
/* If true, the shape may have small array index properties 'n' with 0
<= n <= 2^31-1. If false, the shape is guaranteed not to have
small array index properties */
uint8_t has_small_array_index;
uint32_t hash; /* current hash value */
uint32_t prop_hash_mask;
int prop_size; /* allocated properties */
int prop_count;
JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */
JSObject *proto;
JSShapeProperty prop[0]; /* prop_size elements */
};

里面指针还用到负操作, 他是数学行家玩的转。
为什么NJS不能这样呢?依赖,各细节之间相互引用。软件开发中没办法的事情。
还以打球为例,那些走位和发力非常老道的球手,打法往往是简单有效的,不要奇怪为什么有些球不先击打进去,而选择更不好打的,一切在掌握之中。

设计重于实现

这是我这两年比较大的体会。以前会觉得有这设计的功夫,早把东西实现好了,而且认为重构能解决一切的设计不足。这是没错的,问题是花了更多的时间在走弯路。
write some code, think, write more, meditate, write a meaningful commit log, take a sleep, think again, and re-read, split/fold/re-write, think, become happy with the final result.
以上是Unit的负责人给的建议,个人觉得这是一种可行有效的方式。NGINX的http2实现就出自他的手笔。对了,NGINX的http3即将完成。

有方法才有可行

本系列文章都会有实操方法。实践对想提升代码的同学是很有效的方式,我个人觉得学习或写项目是一种方式。
utopia是我写的一个API网关框架,只有一千行代码。里面的一些设计就参考 Unit,尤其是路由部分。我了解他们的设计历程,非常优秀。这是一个非常适合学习的项目。
设计可以聊的实在太多,远不止一文可以讲完,以后会不断的夹杂在其它章节。

[nginx-lua-module] https://github.com/hongzhidao/nginx-lua-module
[the-craft-of-programming] https://github.com/hongzhidao/the-craft-of-programming
技术问题欢迎issue里交流
[utopia] https://github.com/hongzhidao/utopia
还未开源,关注公众号及时了解更新
gzh

已修改于2023-03-08 02:05
本作品系原创
创作不易,留下一份鼓励
洪志道

暂无个人介绍

关注



写下您的评论
发表评论
全部评论(1)

按点赞数排序

按时间排序

赞同

0

回复举报

发表于2020-11-04 22:08



回复jiangyidigital
回复
关于作者
洪志道
这家伙很懒还未留下介绍~
11
文章
0
问答
54
粉丝
相关文章
介绍nginx网页配置工具QQ技术交流群1:1106758598QQ技术交流群2:560797506邮箱: cym1102@qq.com官网地址: http://www.nginxwebui.cn码云: https://gitee.com/cym1102/nginxWebUIgithub: https://github.com/cym1102/nginxWebUI功能特点nginxWebUI也可管理多个nginx服务器集群,随时一键切换到对应服务器上进行nginx配置,也可以一键将某台服务器配置同步到其他服务器,方便集群管理.部署此项目后,配置nginx再也不用上网各种搜索配置代码,再也不用手动申请和配置ssl证书,只需要在本项目中进行增删改查就可方便的配置和启动nginx。技术说明本项目是基于springBoot的web系统,数据库使用sqlite,因此服务器上不需要安装任何数据库项目启动时会释放一个.sqlite.db到系统用户文件夹中,注意进行备份本系统通过Let'sencrypt申请证书,使用acme.sh脚本
点赞 6
浏览 6.4k
  前三周学习了陶辉老师的“NGINX基础培训系列课程”,感觉受益良多,在这里想把一些知识点记录一下,和大家分享一下知识点,也方便日后的随手查看,温故知新。  首先,我们了解到了Nginx的版本,Nginx发布版本分为主线版本和稳定版本,区分两个版本也非常简单,主线版本版本号为单数,比如1.19,稳定版本为双数,比如1.18,今天我要说的是稳定版本,这个版本会尽量少的减少Nginx的bug问题,适用于生产环境,这里我不建议使用Nginx和其他软件一样在生产环境中落后一个或多个大版本使用,之前生产环境做过漏扫,发现我们编译自带的Nginx版本为:nginx/1.13.3(查询命令为nginx-V),结果出现了多个漏洞,四个高危和一个中危漏洞:        通过升级Nginx到稳定版最新版本后修复!  其次,是Nginx发行版本的选择,目前比较流行的有:nginx、nginxplus、Tengine、openresty、ope
点赞 1
浏览 3.5k
感谢您参加“NGINX从入门到精通进阶系列培训”!以下为培训的问答、课件和录像,希望您能通过此培训学有所得,祝学习进步!>问与答:- 基础篇+高级篇 - 应用篇+实战篇(New)>课件(PPT):基础篇:-NGINX概要、安装、配置:https://interact.f5.com/rs/653-SMC-783/images/CNFEB22-NginxCoreCourse-Setup.pdf-NGINX日志、运维:https://interact.f5.com/rs/653-SMC-783/images/cnfeb22-nginxcorecourse-maintenance.pdf高级篇:-NGINX变量、API:https://interact.f5.com/rs/653-SMC-783/images/CNFEB22-NginxCoreCourse-API.pdf-NGINXSSL、NJS:https://interact.f5.com/rs/653-SMC-783/images/CNFEB22-NginxCoreCourse-SSL.pdf
点赞 10
浏览 4.9k