工程师手记-布尔计算器

工程进度

2016-12-02

  • 添加软件声明 1h
  • 添加免责声明 1h
  • 升级Simple框架DB接口 2h
  • 升级服务器端OS为Ubuntu 16.04 1h
  • 升级PHP版本到PHP7.0 1h

2016-12-01

  • 画出logo,并添加到app上 2h
  • 调整title bar 2h
  • 排查加载闪屏问题 1h
  • 调整个税计算页面样式

2016-11-30

  • 升级PHP框架 2h
  • 制作了首页 3h

2016-11-29

  • 把PHP框架进行改造,加入命名空间 1h
  • 把PHP的模块和配置进行分离,依赖注入 1h
  • 把布尔个税计算器页面进行重构,使用php模块化,不再是一个单独的html 1h

工程师手记-升级PNI以支持PHP7

本文内容简介

  • 简要介绍本博客写作背景和目的:升级PHP Native Interface的代码使其支持PHP7。
  • PNI 升级以支持PHP7的过程做简单介绍。
  • 对这次升级的思路和方法进行总结。
  • 思维发散,假设其他情况下应该用什么样的方法进行升级php7的扩展。

背景和目的

PNI在2016年10月之前仅支持php5系列的版本。9月份时进行升级,使其支持PHP7。

PNI是什么? 具体参考这边博文《PHP Native Interface》。PNI代码规模只有1000行左右,升级大概花费了一周时间(工作外时间),其中包含2天寻找思路、2天的代码升级,3-4天的问题排查。

这篇博客记录了一下PNI升级过程,包括自己学习PHP7扩展框架、API定义的过程,调试、压测、修复bug的过程等。

把这些东西记录下来,主要是为了总结出自己的学习方法和操作方法。写成博客后,可以更好地帮助自己反思升级过程中是否有哪些不好的地方。当然其中也会有自己的成功经验。

PNI 升级过程简述

面临哪些问题,以及有哪些不确定的地方?

  • 代码从何处改起?
  • 如何保证编译通过?
  • 功能能否保证不变,以及PNI自身定义的PHP接口是否需要发生变化。
  • 升级过程中遇到bug和陷阱怎么办?
  • 这次升级的代价有多大?

升级PNI,从哪入手?寻找突破点的过程。

用搜索引擎搜到了几篇博客,都是介绍PHP7和PHP5的 zend api 不同的网文。随意浏览了一下,感觉帮助不是特别大。

自己下载了PHP7.0的源代码,编译一下,稍微看了一下 Zend的源码(主要看Zend_API、zend_list、zend_hash),少数几个ext里的扩展的源码。

看了后收获特别大, 了解了 php7扩展编写的几个特点:

  • php7 ext 扩展编写框架结构变化小
  • API函数名称变化小,多数无变化
  • API函数参数列表变化大
  • 宏的变化小

升级的思路就有了。

升级方法?

依据发现的php7扩展代码的特点,决定以PHP5版本的PNI的代码为基础,进行升级。代码不需要做太大的变动,更不需要完全重写。

设立3个目标,并按顺序分步骤实现。 1. 升级代码,编译通过。 2. 功能验证通过。 3稳定性验证通过。

为了达到第一个目标,使编译通过,使用下面方法:

  • 参考其他extention,首先对比php7和php5扩展的框架,优先修改PNI的框架。

  • 对比PHP7 源码中的zend_API.h、zend_list.h、zend_hash.h等。

  • 要修改的函数多,可能会有API没有升级被遗忘怎么办? 所以一边升级代码,一边make,看着gcc的错误提示去一点一点改正代码。

达到第2个目标的方法,则多写功能验证的测试case。

达到第3个目标的方法,是做压力测试,观察内存和cpu的使用情况。

  • 观察变量和资源是否被及时回收,是否有内存泄漏
  • 观察cpu负荷是否过高

在升级过程中遇到过最大的一个问题是,pni中遇到了内存泄漏。为了定位内存泄漏的原因,花了非常多的时间。

  • 打日志,定位内存泄漏逻辑。
  • 定位后,添加efree、free等函数调试。
  • 添加efree逻辑无效,打印对象引用计数的个数
  • 找到原因,是存储资源类型的变量,作为另一个对象的属性时,如果对象释放,存储资源类型的成员变量不会主动释放。
  • 解决办法,在对象释放的析构函数中,添加释放资源的逻辑。

总结

总体上说,这次升级PNI比较顺利。

在做的过程中,思路其实并没有上面写的那么清楚,心中也只有个大概思路。

在刚开始时,寻找从何下手的过程中,还是走了一点弯路,但浪费的时间不多。

走的最大的弯路则是,上文说的定位和解决pni内存泄漏的过程中, 没有首先想到去观察对象或变量的引用计数的。这使pni升级的过程直接阻塞了。今后遇到内存泄漏的情况,应该首先观察zval的引用计数数目是否为0.

思维发散

下面开始做各种假设,假设我会面临不同的情况(实际并不存在),我该设计什么样的思路和方法进行PNI的升级呢?

  • 假设PHP7和PHP5扩展开发的框架和结构有大的不同,对PNI的升级,完全重写也许是好方案。

  • PNI的代码规模只有1000行,假设10W行规模以上,我在达到上文说的,第1个编译通过的目标,就不能直接用gcc的错误信息去定位没有升级的代码,不能用一边改代码一边查php7 zend api代码。应该把PHP7的代码都给熟悉了,再去升级代码。

  • 假设升级代码规模大,php7的扩展框架和代码与php5也有大不同呢?学习成本和工程量将变得非常大,这种情况怎么做呢? 1.大规模的代码,逻辑上应该做好分层和抽象。 在升级大规模代码前,应该先写一个小的PHP7扩展,拿来练手,这样学习的曲线就会平缓很多。

Web服务压力测试工具BullBench

什么是 BullBench ? (what)

  • 一个可以对web服务进行压力测试的工具
  • 最大特点,BullBench 可以读取 nginx access log 中请求,并将其发送给web服务。
  • 也可以读取自定义文件,使用正则匹配和替换,定制请求,发送给web服务。
  • 可以同时模拟15000并发度请求(具体数值受限于系统配置)
  • 和bullbench类似的软件有 webbench、tcpcopy、jmeter等,与它们相比,bullbench有自己独特的地方,比webbench功能多,比tcpcopy操作简单,比jmeter编写测试用例的学习成本更低。

代码托管地址 : (where)

https://github.com/zuocheng-liu/BullBench

为何编写这个软件? (why)

主要原因是,我曾经在查找一个系统内存泄漏问题时,遇到了一些阻碍:

  • 线下压力测试时,要尽可能模拟线上请求
  • 理想方案是使用tcpcopy,复制线上流量,但是目前没有现成的tcpcopy环境

为了克服这些阻碍,就写一个简单的工具,先是读取nginx access log, 提取请求uri,然后模拟1000个客户端发送给Web后台。

这个工具经过再完善和变得更通用之后,bullbench诞生了。

什么时候用呢? 以及软件的局限有哪些。(when)

使用场景:

  • 压力测试时
  • 追踪bug,复现线上问题

局限:

  • 如果没有请求日志,无法使用bullbench进行压力测试
  • 只能模拟发送HTTP GET请求
  • 不能够处理 HTTP 3XX 重定向服务
  • 其他

作者联系方式 (who):

如何编译? (how )

进入源代码目录,执行make

如何使用?

执行 ./bin/bullbench -h 有详细说明和使用实例

    BoolBench 1.0

    -f <filename> 包含请求的文件,可以是nginx access log,也可以是自定义的数据文件
    -t <num>      请求文件的类型, 1 是 nginx access log, 0 其他 其他, 默认是 1
    -u <url>      请求的url前缀, 不支持 https, 比如 http://www.bullsoft.org
    -H <host>     HTTP请求头字段Host, 默认是 NULL
    -c <num>      并发请求的线程数, 默认是 1000
    -r <regex>    正则表达式,用于提取请求文件中特定的内容. 必须和参数'-t 0' 搭配。
    -o <string>   正则变量拼接后的字符串, 支持 $0-$9
    -h            显示帮助信息
实例1:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080
实例2:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080 -H www.bullsoft.org
实例3:  ./bullbench -f /var/log/nginx/access.log -u http://127.0.0.1:8080 -t 0 -r "[a-z]*([0-9]+)([a-z]*)" -o "/display?a=\$1&b=\$2"

一些参数(how much? how many?)

并发线程数设置多少,依据系统情况而定。

  • 在并发线程数过高的情况下,会打开非常多的连接,常会遇到打开文件数过多的错误
  • 使用root通过ulimit设置 open files的限制,使之变大

工程师手记-将Memcached内存管理机制移植至Redis

Idea 的提出

  • Redis 有其高效的异步网络框架
  • Memcached 有其高效的内存管理机制

将这两者结合在一起后,会如何呢?开始试验将Memcached内存管理机制移植至Redis。

本篇博客的姊妹篇链接: 《工程师手记-将Redis异步网络框架移植至Memcached》

调研和选型

Redis内存管理的几个缺点:

  • 使用tcmalloc 或者 jmalloc 库,这两个库封装较重,内部特性也较多。
  • tcmalloc 适合小空间分配,稍大的空间分配会有瓶颈。
  • Redis 主要是单线程运行(只在后台任务cache持久化功能处又启动了新线程), tcmalloc 和 jmalloc 有保证线程安全,但对redis来说是不必要的功能。尤其是jmalloc,为线程安全做了很重的设计。

软件选型

  • 并不是把 Memcached 的内存管理直接替换redis的内存分配,而是使用ae-memcached的内存分配方式。
  • ae-memcached 的内存分配和 Memcached在原理上毫无不同,仅是从软件架构上对其进行重构和优化。具体参考:《AE-Memcached 优化记录》
  • 选择 Redis 2.8.24 作为移植受体

Redis代码修改和编译 / 移植方案

  • 从ae-memcached中拿出mem_cache / slab 两个类,直接移植到Redis src 目录中
  • 新建两个文件 mc_malloc.h mc_malloc.c,封装mem_cache,让其提供类似 malloc、 alloc、realloc、free的接口
  • 修改 zmalloc.c zmalloc.h 这两个文件,让其支持mc_malloc
  • 修改 Makefile ,默认MALLOC 使用 mc_malloc
  • 修改bio.c 文件,把zmalloc 和 zfree用 libc的 malloc 和 free 代替,这么做主要考虑到线程安全
  • 编译、运行

代码托管地址

给新的redis起了一个新名字mc-redis,源代码托管于Github上:

https://github.com/zuocheng-liu/mc-redis

性能测试实验

硬件

  • Redis-server 服务端 GenuineIntel 6 Common KVM processor 6 核 2.0GHZ 4G 内存
  • redis-benchmark 和服务端部署在同一台服务器上

测试方法

  • 分别运行原本Redis 和 mc-redis, 分别作为实验和对照,参数为 redis-server –port 7777
  • 启动Redis,运行redis-benchmark 测试三次。重复前面步骤,Redis共重启3次,redis-benchmark共测试9次。
  • mc-redis 的测试也使用上面方法
  • 测试命令 ./redis-benchmark -h 127.0.0.1 -p 7778 -q -d 100
  • 只观察set / get 命令的并发度

测试结果

启动一次redis,做了三组实验,数据如下:

  • mc-redis GET 62972.29 / 58275.06 / 55897.15 (requests per second)
  • redis GET 47281.32 / 62034.74 / 51759.83 (requests per second)
  • mc-redis SET 64808.82 / 59031.88 / 56915.20 (requests per second)
  • redis SET 51733.06 / 53676.86 / 56947.61 (requests per second)

结论

在刚启动时(预热阶段),mc-redis 的 set 和 get 操作,比原版redis 的并发处理能力高大约有 15%-20%。 但是稳定运行后, mc-redis 和 原版redis,性能相差较小。

AE-Memcached 优化记录

优化背景和目的

  • 学习Memcached 代码
  • 将 Memcached 的代码成为自己的技术积累
  • 优化Memcache 代码,提高自己系统分析能力

源代码托管于Github上:

https://github.com/zuocheng-liu/ae-memcached

性能优化

网络模型的优化

  • 网络IO多路复用 + 单线程

  • 将 Redis 异步库 移植至 Memcached

优化动态申请内存机制

  • 使用预分配,减小系统调用 malloc、realloc、free的次数,主要出现在新建/关闭链接时,会有较多的系统调用

部分小的函数使用宏代替

优化Memcache协议命令的解析

  • 调整各个命令的解析顺序,把get 和 set 命令放到最前面

软件架构优化

软件架构优化,保证关键代码性能不变

使用宏加强代码复用

  • 重构verbose日志
  • 重构网络库
  • 重构slab

命令模式重构 Memcache 协议

  • 创建command_service类,统一管理命令的解析、处理

更深层次的抽象

将 stats 、 settings 、 logger 和全局资源进行抽象

解耦

  • 将各个模块接口化,减少模块间耦合,尤其是 slab item memcached之间的耦合
  • 依赖注入原则,增强各个模块的复用,其中mem_cache模块 settings等可以形成框架。
  • logger
  • command service

工程师手记-将Redis异步网络框架移植至Memcached

Idea 的提出

  • Redis 有其高效的异步网络框架
  • Memcached 有其高效的内存管理机制

将这两者结合在一起后,会如何呢?

本篇博客的姊妹篇链接: 《工程师手记-将Memcached内存管理机制移植至Redis》

调研和选型

Memcached 的几个缺点:

  • 使用封装较多的 libevent 异步库
  • Memcached 1.2.2 版本后,开始使用多线程,而多线程上下文切换、互斥锁的竞争带来了一定的性能开销
  • 每次新建tcp 连接都进行系统调用(malloc)申请空间

对Memcached 的一项性能测试

实验验证,多线程不会对Memcached带来性能的提高

参考链接:

对Memcached 1.2.2 的一次基准测试实验

软件选型

  • 从 Redis 3.2.0 (截止此文最新稳定版) 版本中选择 libae、 libanet (ae.h、ae.c、anet.h、anet.c 四个文件)
  • 选择 Memcached 1.2.0 作为移植受体

选取这两个版本的主要原因是,作为Redis 和 memcached 的早期版本,特性较少,代码复杂度低,适合进行初步实验移植。

为什么不直接使用epoll呢? 因为除了异步框架之外,还需要定时器的功能,而redis的异步库中已经有现成的实现。

代码托管地址

给新的memcached起了一个新名字ae-memcached,源代码托管于Github上:

https://github.com/zuocheng-liu/ae-memcached

性能测试实验

硬件

  • Memcached 服务端 GenuineIntel 6 Common KVM processor 6 核 2.0GHZ 4G 内存
  • Memcache 客户端 和服务端相同的另外一台服务器

测试方法

  • 分别运行Memcached 和 ae-memcached, 参数为 memcached -m 1024M -t 6 , 其中原本memcached运行6个线程,而ae-memcached 是单线程
  • 使用memslap 1.0.2, 测试10W次请求,100的并发度的情况下,memcached的处理时间
  • 测试命令 ./memslap –servers=test-server:11211 –concurrency=100 –execute-number=1000 –tcp-nodelay –non-blocking

测试结果

  • ae-memcached 6.709 / 6.878 / 7.362 / 7.196 (seconds) 平均7秒
  • 原版 memcached 5.079 / 5.043 / 5.069 / 5.206 (seconds) 平均5秒多

结论

原版Memcache 处理10W条数据的时间要比 ae-memcached少很多。多线程的确会给Memcached带来性能的提升.我们前面的假想并不正确。

对 Memcached 的持续优化

本文作者不仅只把Redis的异步库移植至Memcached,还对Memcached进行了持续的优化,详细请阅下面链接:

http://it.zuocheng.net/ae-memcached-optimization-zh

敏捷软件开发 – 总结

敏捷软件开发简介

  • 一套适应变化,立足于变化的软件开发方法
  • 一套被证明过的目前最有效的软件开发方法

Scrum、 XP 和 敏捷的关系

  • 敏捷是一种指导思想或开发方式(价值观)
  • Scrum和XP是敏捷开发的具体实现方法 (方法论)

敏捷和瀑布

瀑布,主要继承自大工业时代遗留下来的思想,关注流程和契约

敏捷,因互联网兴起一种软件开发思想,应变化而生,关注反馈和沟通

互联网软件开发的特点

  • 需求总是在不断变化

  • 不清楚需求最终是什么,一切需求都来自于未经验证的假设

  • 对软件系统和团队的要求,必须快速适应变化

  • 互联网时代的产品需求来源已经发生变化

  • 传统IT的软件需求来自于客户,软件开发商必须基于合约按期向客户交割软件产品。(契约)

  • 互联网应用软件,以服务提供给用户/客户,需求来源于互联网企业对用户/客户体验或服务自身的挖掘。(变化、不确定性)

互联网开发的问题

  • 什么样规模的团队最能保证效率和安全?

小团队

  • 什么时候才能确定最终的技术方案

编程的时候

软件开发方法的演化过程 (敏捷开发的推导过程)

  • 小作坊式开发

  • 传统瀑布模式(螺旋模式等等一系列模式)

  • 迭代式开发

  • 敏捷开发

Scrum 流程简介

参考维基百科

感性体验

对敏捷的反思

  • A/B 实验

需求是不确定的、易变的,原因之一是因为需求往往来自于假设,对假设的验证可以进行A/B实验。

  • 灰度发布

保证安全,减小风险。

  • 技术驱动

技术驱动能最大的激发团队成员的积极性。

问题

  • 敏捷适合小团队?

并非如此,按照敏捷的思想,团队要保持精简。若原有团队是大团队,则拆分为小团队。

安全的C/C++网络应用的开发流程

本文概要

简要介绍C/C++ 网络应用系统的特点、应用场景,简述适用于C/C++ 网络应用的开发流程。

C/C++ 网络应用系统特点

  • 高性能
  • 高吞吐量
  • 节省内存
  • 开发、测试用时多、开发效率慢
  • 调式成本特别高

C/C++ 网络系统的应用场景

  • 数据接口服务
  • 计算密集型应用,比如搜索引擎、图像处理、统计学习等
  • 特殊领域应用,只能由C/C++ 完成

开发流程的目标

由于上面提到的C/C++ 的特点和应用场景,因此开发流程要达到下面的目标:

  • 安全
  • 控制bug
  • 保证收益

开发流程

需求

因为C/C++ 系统的自身特点,在网络应用中,它们常常担当后端系统、基础服务等。在整个产品的系统架构中,C/C++系统和业务系统常常被分离开来。C/C++ 系统不直接响应来自产品的需求,只响应业务系统提出的技术升级或改造。

设计

  • 日志一定全面,Debug、info、warning、error、fetal
  • 使用参数开关来控制新加的特性,对新的特性一定要追加日志
  • 单例测试覆盖率要尽可能高
  • 对输入的安全检查一定要做到位
  • 可扩展性一定要高

编码

  • C/C++ 代码要遵守代码规范和代码标准
  • C/C++ 代码书写尽量遵从ANSI/ISO 标准
  • 不要使用生僻的语法
  • 代码可读性和可维护性一定要高
  • 一定要书写单例测试

测试

  • A/B 测试

测试环境的搭建

必须多套测试环境

  • 普通测试环境
  • 压力测试环境
  • 沙箱环境
  • 线上小流量环境

必须在每一种测试环境都测试完成后,才能发布到生产环境或在生产环境上推全流量

发布

灰度发布

工程师手记-从PHP工程师到C/C++工程师的转变

本文分享一下,博主从PHP工程师变为C/C++工程师后的一些真实感受。

两句话写在前面

  • 编程语言只是工具
  • 工程师应该用正确的工具干活儿

但从我现在的经历和感受看,两句话真的不靠谱。

简单介绍转变的过程

2012年大学毕业,进入第一家公司,担任PHPer。之后两三年用的编程语言一直是PHP,即使14年中间换过工作,依然写PHP,做的都是公司后台的业务系统。

2015年,语言转为C/C++,负责公司商业广告投放系统检索端的开发。

技术方向的不同

  • PHP 关注业务的实现
  • C/C++ 关注底层

PHP程序员和C/C++程序员日常技术关注点

  • PHP 程序员关注对业务的抽象、关注系统的可扩展性,关注如何设计业务架构,使用什么样的设计模式等等

  • C/C++ 程序员更加关注系统的性能,系统的可伸缩性,关注使用什么样的工具提高性能,关注网络IO模型,数据结构和算法等等。

安全性

  • PHP 程序员关注的安全主要是避免系统漏洞和防止攻击,sql注入、XSS攻击、文件注入攻击等等, 系统安全比较重要。

  • C/C++ 程序员更关注逻辑的安全性, 比如输入输出的边界,异常处理等等,系统稳定最为重要。

PHPer 和 C/C++ 工程师何时相互欣赏?

  • PHPer 看 C/C++, C/C++ 好高级啊,底层实现都知道的那么清楚。我们了解就没有那么深啊。

  • C/C++工程师看PHP, 你们做的那么多应用好高级啊,那么复杂的金融系统,你们怎么就做出来了,如果用C来写,怎么才能写出来呀!

PHPer 和 C/C++ 工程师何时相互鄙视?

  • PHPer 看 C/C++, 你们代码怎么都是面向过程的,可读性那么低,多不好维护,抽象和复用都搞不好,软件架构乱成啥了,软件稍微大些,你们就要拆成服务。

  • C++ 工程师 看 PHP, 你们设计这么多类,代码那么啰嗦,难道不是不过度设计吗?你们写的代码性能能行吗?明明10台服务器就能满足的PV,竟然要200台!

同时有了PHP 和 C/C++ 背景之后,我的工作变成什么样子了?(以下故事基于事实,但与事实略有差异)

  • 老板:“那个项目是PHP的,你来接吧。” 我:“NO”。 老板:“只能你来接啊,我们辛亏有你啊,组里只有你会php啊,您一定要接啊!” 我:“OK。”(幽怨+无奈)

  • 猎头:“我这有PHP职位,薪水丰厚”。 我:“俺转C++了”。 猎头:“C++需求少啊,工作不好找啊”。 我:“俺还要写c++”。 猎头:“看那个PHP职位,背景待遇好”。我:“……”。

  • 某一线公司HR :“你C++履历太短,暂时还不适合我们的职位。” 我:”……”(心里流出两行泪)。HR:”但我们公司有PHP职位啊,你来呀”。我:“俺不去了……”(秋风吹皱偶滴心)。

博客写完了, 现在感觉,开头那两句话,说的无比正确。

工程师手记-PNI

PNI 简介

PNI,是PHP Native Interface的简写。它是PHP的一个扩展,可以通过它,让PHP直接调用C语言写的函数。

想法源自在百度做项目时,不时地会面临同样一个问题,PHP该如何直接快速地与其他语言交互呢?

PHP 扩展的不足

用C写PHP的扩展是常规的方法。不过使用这种方法总是要面临诸多问题:

  • 作为开发者,要去学习PHP-API、Zend-API,这是不小的学习成本,而且用C做开发测试的效率都不高。
  • 写好了PHP扩展,怎么部署呢,还要找运维工程师去讨论、去争取后才能上线。

来来回回项目进度就被拖慢了。

有如此技术痛点,就找个通用的法子吧。

调研过程

JNI 和 Python

Java 可以通过JNI调用C/C++,Python也有相应的包,比如ctypes

HHVM

稍微去翻了一下HHVM的扩展,发现HHVM有 native interface, 但稍微看了一下,却发现那只是HHVM的native interface,PHP无法使用。

这个项目,模仿JNI,就取名PNI吧

c 对动态链接库的动态调用

c 加载动态链接库及调用函数有现成的方法。

使用dlfcn.h 库中的dlopen、dlsym、dlclose三个函数就够了。

设计和实现过程

接口设计

最开始的方案,就是模仿JNI。因此在最初的实现里,PNI对动态链接库的查询,调用函数的方法都直接模仿了JNI的接口实现。测试使用时,却感觉PNI的接口非常不友好。

和Java、Python 不同,PHP是弱类型语言。JNI 可以在给函数传递参数时,参数的数据类型是已知的,但是PHP传的都是zval,类型并不可知。我把数据类型的控制交给了开发者,在PNI的扩展里,通过struct zval 中 type 字段判断,此种方案让PNI很难于被使用。

于是改进方案,在PNI里添加PNIInteger、PNIDouble、PNIChar和其他几个可以标明参数数据类型的类。

参数压栈

PHP 的函数调用后,动过dlfcn.h库,可以找到函数地址,但是如何调用函数呢?调用函数又如何传参呢?我们无法知道所调用函数的参数列表是什么样子。

由于没有找到比较好的方案,所以就借助于汇编,使用汇编模式C语言的参数压栈方法。

于是写了很多种C语言的函数,主要是参数列表不一样,总结了GCC编译器在x86_64架构CPU下参数压栈的几个特点:

  • 8、16、32位和64位整形的参数,按顺序传到64位的寄存器,当多于6个整形参数时,剩下的整形参数都压栈

  • 32位和64位浮点,都传到64位的浮点计算器,浮点参数多于6个时,剩下的浮点参数都无效

无论C函数的参数是什么样的类型,PNI都将其按64位的整形或浮点处理。

问题解决了

系统兼容性

因为参数压栈的问题,目前PNI只支持GCC编译器和x86_64架构的CPU。其他架构和编译器都没有来得及去实现。

总结

吭哧吭哧,PNI最初版本调试测试成功。

后记

如何让PNI被更多的人知道并使用,怎么做呢?(还在思考中)

  • 开源,传到GitHub
  • 融入社区
  • 在问答网站上多回答相关问题

其他方法呢?再想一下。

工程师手记-赶集易洗车后台和服务端

项目简介

赶集上门洗车业务服务器端,分为两部分:

  • 为洗车APP提供服务的接口
  • 洗车业务相关的管理后台

从整个项目上说,虽是个创业项目,但需求针对性强,从洗车仅一点上切入,易于发力。

相比曾经经历一些创业项目中,产品总是摸不住方向,不知何处发力,试错成本太高,做到最后都迷茫该做什么,怎么做。还有些项目,铺开面去搞,结果摊子太大,顾及不暇。

赶集易洗车与之相比要好太多,对我来说,做这个项目感觉很舒服。负责时间:2014-11 至 2015-08

框架代码

主要开发语言是PHP。典型 LNMP 架构。

代码框架是赶集网多年积累下来的老框架,对于老框架,工程师都是能够理解的,不过还是想说一说。

  • 简易实现的路由分发
  • 没有清晰的分层概念
  • 有一套简单的DB访问工具类
  • 对其他架构系统调用封装访问很全面
  • 赶集所有频道都使用一套框架代码

总之,有优有劣。

业务代码

虽然使用了类,但仍然使用了面向过程的思维在开发。代码复用性、可扩展性比较低。由于业务简单代码耦合还不是很严重。

代码没有分层也是问题。

项目发展过程

框架的改进

赶集易洗车框架和其他MVC框架的诟病一样,表现层逻辑直接调用数据访问逻辑,并没有业务逻辑层。于是劈出一个目录,存放业务逻辑层代码。建立规范约束DB层和表现层,老的业务逻辑代码日后慢慢迁出和重构。

面向对象

老逻辑中类的作用只被用于聚合。多态和继承并没有被使用。

对DB层的再次封装

在DB访问类,由于历史原因,用起来特别不友好,也有让新人不易察觉的逻辑陷阱。

对此,我使用了Service Locator Pattern + Proxy Pattern 这两个设计模式,对原DB层再次进行封装,简化上层对DB层的调用,添加缓存的优化。之所以使用这个方案,主要是根据项目当前情况,有如下考虑:

  • 不能抛弃和重构原有的DB访问类, 因此选择再其之上再进行封装。遵循开闭原则,不对老逻辑进行改动
  • 原有DB访问类有逻辑陷阱,因此使用Proxy Pattern,把陷阱的规避放到proxy类中,优化对外的接口
  • 原有DB类,每一个数据表都对应一个DB类,全局只需初始化一次,但会在不同的地方多次调用,所以选用Service Locator Pattern,每个DB类,初始化1次便缓存起来,供之后逻辑重复使用

用了上面的方案后,代码可维护性和可读性都提高了。

展示列表的封装

后台有很多的展示列表页面,主键往往是一样的,比如都是订单的ID,但是其他的列,有的一样,有的不一样。可以看成,不同的列表页面,就是不同的列的组合。

使用Decorator Pattern 对其进行重构。这个设计模式比较简单,但是实在是特别好。在我之后的开发工作中,凡是遇到列表,开发的时间估计就能缩小一半。维护的成本则是更低。

具体业务开发

具体业务的开发,也许是最枯燥的工作,不具有挑战性,只能付出劳力。估计只有在机器人程序猿出现后才会解决这个问题。

关于团队

团队每周有分享,可怜创业项目排期紧张,团队分享没有坚持几期,后来慢慢停掉。原因我认为可以从两个方面看:

  • 随着业务压力的增大,团队给予技术成长上的提升关注也会消弱。
  • 人有惰性,团队往往也是如此。

我在团队分享3次,《地理区域和点关系计算》、《代理和反向代理》、《PNI》 , 分享积分是团队最高的,本来有一部kiddle做奖品,团队解散后也没去要过来,哈哈。

开始写工程师手记

从12年4月大学毕业开始实习,至今工作3年半经验。

回忆这三年半,这些经验都给我带来了什么呢?不容置疑,有很多东西,但是具体有哪些呢?不好好收拾一下脑海,真就不能立刻说出来。

所以昨晚上班路上就想出了写工程手记这么一个idae,有了手记,我就不会对做过的项目有种“彻底忘记”的感觉了。自己做过什么,一看手记立刻就能回忆起来,曾经的经验、走过的陷阱、当时的思考,都可以立刻回到自己的身上。

通过工程手记,还可以记录在做项目过程中的所感和所想,以后也可以拿来回顾和反思,说不定能得到更多的东西。

总之,就是让自己以往的经历,不仅仅只是流逝的时间,只是过往的履历,一定要变成有价值的经验。

手记的内容

  • 项目简介 (what 、 when 、who)
  • 所遇问题 (why)
  • 如何做的呢? (how)
  • 总结和反思

给自己的手记制定几点原则

  • 原公司保密的东西不以泄露
  • 从客观角度描述问题,尽可能不夹带个人感情倾向
  • 不随意否定项目、团队或个人
  • 内容保持精简,不谈无关话题

写手记的想法是怎么来的呢?

主要是源于两件事吧。

刚玩微博时,关注了易胜华律师,从他发的内容里知道他有写律师手记的习惯。他的手记里,都记录着每次办案的一些重要或关键的细节,也包括他思考的过程。看了后深感佩服,不愧是名律师啊。

在大学时,曾是学校越野队队员,队里规定每次比赛完必须要写比赛总结。总结的目的很明确,就是经过每次的比赛,都能让队员有所提高。

所以我们软件研发工程师为什么不能有手记呢?

单例测试总结

最近写了比较多的单测,对测试用例做了一下总结。          

网上也有很多讲如何写单例测试的教程和经验总结,而且都比较系统。但这里还是总结一下自己在写单例测试时总结的经验。

1. 经验总结

1).时间和对象          

只对比较稳定的代码写单测。        
从反面讲,还处于经常修改状态的代码是不稳定的代码,针对其写的单测也必然是不稳定的,随着设计和代码的不断更改,单测也需要不断更改。

2). 去除底层依赖          

对一段代码写单测时,其中提供API、IO和DB数据的类或对象应该mock掉,但对这些类也要单独写单测。        
一方面,这些类对系统环境有很大依赖,当环境出现问题时(比如API的服务、DB宕掉),单例测试将不能进行;另一方面,这些类的效率一般比较低,不mock掉会增加单测运行的时间。

3).写单测的误区        

只为覆盖率而写单测。        

因为单测覆盖率往往成为一个系统质量的考核目标,所以在写单测时,可能只为提高单测覆盖率而写单测,过少地从业务的角度去写单测。单测应该是保证系统在各种业务场景下,系统仍能正常工作的工具。行覆盖率或者分支覆盖率的提高,并不代表不同业务场景的覆盖也会提高。

2. 对以后写单测方式改进

1). 建立自己的测试样本工具,比如可以自动生成的不同角色的用户的工具。这样方便或者更能全面地对系统的业务进行自动化测试。

2). 写单测时更多地站在业务的角度,而不是代码的角度。

产品开发过程中的经验

  1. 对于文本的处理,要考虑到文本可能的来源,对于文件中文本,不仅要用trim加工,还要将原有格式去掉。比如从excel专成csv文件,会多出引号。

    1. 对于业务中的一些需求和情况,在系统中的逻辑可有可无,最好的是把逻辑加上。比如银行加密箱号是unique,在数据库中最好将加密箱号设为unique