复杂度其实是降维

突发奇想(可能也没啥新奇的,也许就是无知而已)

其实代码中控制复杂度的过程就是降维,或者说把一个复杂的结构用多项式来标识,每个项就是一个类(对象),然后把他们加起来。

如果做不到这一点,那么代码的复杂度将不可控,整个系统也就越来越不可控,最终烂掉。

貌似,代码也有它的生命周期,很少有代码能够“活到”足够长的时间,而变得不可控。

想象,一个函数,里面是一大堆的ifelse语句,每个if就是一个分支,每个分支就是一个乘法,每多一个乘法就是一个新的维度。所以我们常常将函数拆分成若干个对象的组合(通常是加法)。

最可悲的事情是什么?

不是和笨蛋做事情,而是不得不为笨蛋做事。

事实是,我也是那个笨蛋。

如果笨蛋无可避免,那么要怎么和笨蛋相处?

大多数类似问题,其实都没有答案。答案在于“选择”。

如果没得选择,那么也就没有必要探讨答案,除了制造焦虑,没有益处。

不能忽视懒B的勤奋程度

在微信提供的PHPSDK开发代码里发现有一段代码,为了转换字符串,而把对象放到数组里再拿出来。

怎么能想出来的~ 用常规的方法不好吗?

能琢磨出这种办法的人,微信应该fire掉吧。话说回来,看来是完全没有code review啊。

有感。

给数据穿上裤子

最近华住又被拖库了,5亿的个人数据就这么泄漏了。感觉好卑微,原来每个人不过是一条数据。

怎么解决这个问题呢?

被“拖库”泄漏数据的原因是:敏感信息在数据库中明文存储。那么很自然的想法是:给数据“穿上裤子”,也就是进行加密。

比如:

电话号码:18633334444 变成 186aaaabbbb ,这里示范的是最简单的字符替换,这种算法也叫凯撒密码。

通过在加密中添加salt来避免太容易的解码,但貌似无济于事。

普通的凯撒加密可以容易地推算出来,即便是更加复杂的加密方式,人家已经获取到数据了,大不了费点事而已,毕竟是对称加密的。

话说回来,如果代码都已经被拖走了,貌似也没什么救了。

但是,对敏感数据进行部分加密还是有意义的,比如,不会容易被“撞库”。

另外,对于数据的提取,毕竟有时候会需要用户的联系方式进行短信营销,以前的做法(也说明了我们对用户的隐私是多么无视)是:直接从数据库里把数据导出来,然后交给短信发送供应商或者程序员通过短信发送接口进行批量发送。这么人的操作很难避免数据不外露,尤其是不靠谱的短信供应商。

所以,从运营的角度来说,也要通过流程来避免人为的数据泄漏。可以通过开发专用的“数据提取”功能来根据权限来获取敏感数据,同时记录下获取历史记录。就算追查追究法律责任时也是有据可查,光是这个“可追溯”也可以很大情况下避免人为泄漏。

顺道发现了一个很不错的关于数据加密的网站:https://www.dcode.fr/

Vagrant使用总结

最开始想要使用Vagrant时,是想要方便地管理和共享开发环境配置,还有一定化的自动化需求。研究了官网,感觉配置还是比较多的,后来就找到puppet,然后又找到一个puphpet(用php开发的一个puppet配置生成工具),就用上了。后来发现,这么使用虽然上手较快,但对Vagrant的配置体系反而似懂非懂,而puppet本身的配置也挺繁琐。

磕磕绊绊倒也是能用,共享开发环境的时候,直接生成一个“笨重的”box,然后发给别人。

provision脚本可以用来初始化环境,而且支持shell、ansible、puppet…,但是本身环境的搭建并不复杂,就没用。所有系统软件都是手动安装的,导致后面只能通过box来进行共享,而不是更优雅的脚本。好在需要共享的人和场景都不多,但每一次都还是很痛的。

最终还是保留使用puppet,相比其它的plugin更灵活、社区也更强一些。

总结一下:

  1. 使用provision初始化环境,需要使用shell或者其它plugin,我使用shell;
  2. 最好别一开始使用工具生成vagrant配置文件,本身并不复杂,否则后面遇到各种蛋疼的问题,搞不清楚到底是生成工具的问题还是配置的问题;
  3. 即便使用了provision脚本,但是如果os升级或者某个软件升级,还是得在shell里操作或者在脚本操作,后续也不得不又通过box共享(或者重新下载新的os镜像+provision);
  4. 一旦维护了脚本,就需要测试。一旦出现了两套东西需要维护,就存在debug由差别(甚至非常细微的差别)造成的各种很猥琐的问题

代码中常见的命名问题

发现在代码命名中有一些常见的错误。我发现大多数是中国人犯这种错误,老外也有。

1. 设计模式后缀

比如 XXXFactory, XXXFacade, XXXObserver 等等,其实完全没有必要增加这种后缀。

2. 拼音命名法

这个我完全是带着个人偏见,相比英文,中文的表达能力更强,也就导致,名字的“意境”更多,更容易误解。倒不是因为使用拼音。

3. 词性错误

类大多数是名词,但如果是command模式,那类名可能会是动词,取决于类的本质行为。方法/函数通常是动词或者动宾短语。

4. 冗余

没有充分使用语言特性,如namespace、package等。比如 AlipayTradeService, AlipayWapTradeService…这种。好不环保哦。

基本上每当出现“重复”时,意味着代码需要重构或者精简了。

5. 直译法

比如,微信的“统一下单”,代码中是 ” unified ” – 统一, “order” – 下单。让我来推测一下这种名字的由来:

最开始,在线上支付和pos系统里有“收单”一说,英文用“acquire”,后来可能pos和线上合并了,也就是“统一”。其实似乎没有增加“统一”的必要。

本质上其实还是收单。

疫苗事件发生后的各种“态度”

年轻人基本没有什么态度,中年人有孩子的赶紧去翻看自己的孩子用的疫苗有没有问题,貌似有问题也没什么办法。

地方媒体纷纷发布各自地区使用的疫苗来源,基本上问题有点严重的也不会发布了,问题不大的或者没有的,连着几天从多个角度进行“全方位”报道。

中央媒体是不能像地方媒体那样的,但目前似乎也仅仅作为“喉舌”,传递高层和相关部门的态度,但仅限于态度。

公知和相对独立的媒体的态度,在发布与被删的边缘游走。

动作?不需要的,也就是短期掉一波原本就不“牢靠”的粉丝而已。普通人们半个月以后就有别的问题需要考虑了。

解决?不可能的,这么难的问题,慢慢来吧……

蛋疼的utf8-bom

测试了一下微信小程序的登录,发现wx.request的返回无法正常地被转换为json对象。

手动加入后一直报错:

仔细一看,返回的json内容最前面有4个“小红点”,鼠标移上去显示是:\ufeff。

google了一下,是utf-8的bom,解决办法是将文件转换一下,去掉bom。

按照这篇文章的方法,发现这三个文件中包含是utf-8 bom编码:

pkcs7Encoder.php

errorCode.php

wxBizDataCrypt.php

也就是微信官方提供的数据加密、解密处理类。

让我比较费解的是,我只是在类中调用了上述三个文件中的类,并且调用也是正常,但是将结果json_encode到前端时就会包含bom。猜想造成这个的原因是那位写代码的工程师是用的windows,然后不小心插入了bom,而且自己是不会发现的。然后所有使用的它的人都悲剧了,因为php不认这几个字符。参考

因为这个问题,耗费了几乎一下午的时间,极其蛋疼。

更蛋疼的是,如果将来上面的三个类有更新(但是没更新utf-8 bom),那么今天遇到的问题还会再出现,这种问题还真是第一次遇到。

PS. 如果是用phpstorm的话,可以直接右键点击文件,然后「Remove BOM」。

PS. 观察到返回结果中“小红点”的个数,就是包含BOM的文件个数。

PS. 一不小心又掉了一坑,这次的现象是微信内浏览时页面的头部莫名出现几个“&#65279”的空字符,还是因为微信的那几个文件,有一个文件在删除bom时候漏掉了。

关于权限访问控制设计

所谓“权限”,可以抽象地理解为:
主体(subject)对客体(object)的部分或者全部属性(attribute)进行的的操作(operation)
“访问控制”中的“操作”可以简化为读(read)和写(write)。
比如,
张三 修改 商品的库存
这里,
主体:张三
客体:商品
属性:库存
操作:修改(write)
通常这里的“客体”的“属性”“操作”也被成为“资源”。
插一句,我们通常所说的角色、角色组什么的,属于对这里“主体”的组织。
*  粒度
这里会涉及到一个粒度问题,也就是,访问控制要控制到多细,一般比较简单的系统中,我们会控制到“客体”这个粒度(也就是直接将属性全部包含到客体里)。
比如,张三修改商品
即,张三可以修改所有商品的所有属性。
但有时候我们会需要将粒度控制到部分属性,
比如,
库管人员(角色)可以修改商品的库存(一个属性)
张三可以修改商品的基本属性(多个属性)
*  过滤
还有一种情况,可能需要主体只能访问一部分客体。
此时,需要根据客体的某些属性进行过滤。
比如,
张三可以修改某个供应商(过滤属性)的商品库存
在描述时,可能是这样的:product{vendor:A}
这里的过滤表达式可能会比较复杂 – 比如还会增加类似条件:价格不高于100元。
类似的,主体和属性都存在需要过滤的情况,属性的过滤也就产生上面提到的“粒度”问题。
*  分配
权限本身也是一种“客体”,权限分配这个动作本身也是一个访问控制资源。
比如,
管理员可以分配商品的修改、读取权限给用户或角色
或者,重新按照上面的逻辑表达:
管理员(主体) 修改 商品的操作权限(客体) 主体(属性)
*  总结
访问控制逻辑的伪代码为:
function access(subject, object, attribute operation)
    permissions = 所有对object的权限定义
    permissions = filterBy({id:1}, stock, write)
    foreach(permissions as permission)
        if(permission.subject filter({role:admin})) return true
    return false
access({role:admin}, {id:1},{stock},{write})

关于用户积分体系

自工作以来有意无意地也做了几个大大小小的和积分相关的项目/产品。做个简单的总结。
先说本质,积分的本质是用来通过一个类似货币的东西来刺激用户活跃,或者说激活(activate)用户。比如虽然现在微信支付正在侵蚀支付宝的领地,但是由于微信支付没有相应的用户体系,所以在这一块算是遇到了短板,下一步再想“侵占”支付宝就很难了。
从定义的角度来说,我觉得英文里的两种说法可以方便区分积分的两个“变种”:
points – 积分点,比如bonus point、loyalty point,最接近货币的概念,所以一般情况下,这个积分点是可以进入账户支付体系的。用户获取成本一般较低,因为对用户的身份和信用度和消费力不是特别关注。
credit – 信用点,更强调用户在系统中的信用度,这个credit可能会和某些特权关联,但通常这种积分不会进入账户支付体系,而是通过独立的兑换中心来完成。用户的获取成本相对比较高,比如必须有下单才能获得。
目前,大多数的积分可能更偏向于后一种,因为直接和支付体系关联会产生财务核算、成本、对账等等一系列操作。但不可否认从用户角度来说“积分当钱花”还是很诱人的一件事。
还有一个问题是,如果积分和货币直接挂钩,相当于创造了一个经济体系,有积分消费、积分产生、通货膨胀、供需决定价格等一系列更大的坑。很多时候,我们会直接锚定一个积分价值,比如对应到1分钱 = 1积分。但如果积分不能很好流通和消费的话,用户可能会更加反感。
我个人偏向credit,并且将积分简化为只能在“兑换中心”或者“礼品中心”使用,同时不锚定积分价值,运营人员可以根据实际情况灵活掌握兑换“汇率”,通过限制兑换数量、周期而获得更多的运营空间。相对主动一些,而不是像前者那样需要不停地监控经济体系的健康状况,每天看着发那么多钱而提心吊胆。如果实在想要实现积分抵现金,可以通过让用户兑换代金券然后来“曲线救国”,当然效果肯定不如“积分抵现”直接,但只要用户对你的抵用券有兴趣或者抵用券确实有价值,而且这种做法的最大好处还是“相对可控的” — 用户兑换多少券、使用多少券都可以进行运营上的限制。
 
再说说数据指标,既然是针对activation,就主要看:积分的获取情况、使用情况,以及积分使用用户的订单转化情况、付费用户比例。
 
但是无论是哪种方式,积分都是一个运营占主导的工具,在体系框架完成后,需要不断进行运营上的优化。