bingoohuang / blog Goto Github PK
View Code? Open in Web Editor NEWwrite blogs with issues
License: MIT License
write blogs with issues
License: MIT License
Fiddle原是小提琴🎻的意思。
图片来自500px。
最早用的是它了,著名的抓包小工具----Fiddler,下面这个样子,当初IE上开发,没有那么好用的Dev Tools。可惜MAC上没有。
MAC上的是Charles
Js在线跑跑脚本,看看样式,最早的应该是JsFiddle。长下面这个样子。
后来,又有了各式各样的版本,比如RunJS、JSBIN、CODEPEN等。随便找一个用用即可。
看下图,最欣赏的是,它把正则表达式进行了解释了,告诉你,你的正则的各个部分是个什么意思。
正则表达式,要注意ReDoS问题
可以支持MySQL5.6, Oracle 11g R2, PostgreSQL 9.6/9.4, SQLite(WebSQL/SQL.js), MS SQL Server 2017,真是超级方便。写完创建表和插入表数据后,就可以在后边写查询语句了,然后执行后,就可以在下面看到结果,并且这时候URL也完成了更新,可以拷贝这个URL,分享当前的SQL给小伙伴们。超级方便。
Graphically, the cron syntax for Quarz is (source):
+-------------------- second (0 - 59)
| +----------------- minute (0 - 59)
| | +-------------- hour (0 - 23)
| | | +----------- day of month (1 - 31)
| | | | +-------- month (1 - 12)
| | | | | +----- day of week (0 - 6) (Sunday=0 or 7)
| | | | | | +-- year [optional]
| | | | | | |
* * * * * * * command to be executed
crontab.guru: — (disclaimer: I am not related to that page at all, only that I find it very useful). This page uses UNIX style of cron that does not have seconds in it, while Spring does as the first field:
Cron Expression Generator & Explainer - Quartz — cron formatter, allowing seconds also.
currentmillis获取当前的时间毫秒数及其换算。
timeanddate日期上的加加减减那些事。
2018年10月24日,汉能给我们发了一些Hogon相关的文档,里面有几封.msg结尾的文件,我们电脑没有安装相关软件,无法打开,然后我就想能不能找个线上的Viewer呢,然后就输入“outlook msg online viewer”谷歌一下,第二项就是它:
然后再看看下面的介绍,支持1200多种不同格式的文件转换,牛B💯不要不要。
最近开了一个新的丽云SpringBoot工程,开始做新的行业SAAS了。结果开发期间,发现日期时间存到阿里云RDS中,会发生偏移,例如存储2018-05-08 00:00:00
,再查询出来,会变成2018-05-07 11:00:00
,造成数据错误。
2018-05-08 10:48:07.869 [http-nio-7689-exec-9] DEBUG tcode[19000001] eql.cn.raiyee.liyun.service.back.dao.MemberCardDaoImpl.eql.addMemberCard.prepare:116 - insert into t_member_card(MEMBER_CARD_ID, CARD_NO, USER_ID, REAL_NAME, CARD_TYPE_NAME, CARD_NAME, CARD_ID, `EFFECTIVE`, `EXPIRED`, EXPIRED_VALUE, EXPIRED_UNIT, INITIAL_BALANCE, AVAIL_BALANCE, `REMARK`, `STATE`, LATEST_ACTIVATE_DAY, FROM_MEMBER_CARD_ID)values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2018-05-08 10:48:07.870 [http-nio-7689-exec-9] DEBUG tcode[19000001] eql.cn.raiyee.liyun.service.back.dao.MemberCardDaoImpl.eql.addMemberCard.params:99 - [148484250576441344, F05, 144902165450399744, 冯小雨, 储值卡, 100元无折扣, 147018595541049344, 2018-05-08T00:00:00.000+08:00, 2018-06-08T00:00:00.000+08:00, 1, 月, 100.0, 100.0, null, 已激活, null, null]
2018-05-08 10:48:07.871 [http-nio-7689-exec-9] DEBUG tcode[19000001] eql.cn.raiyee.liyun.service.back.dao.MemberCardDaoImpl.eql.addMemberCard.eval:106 - insert into t_member_card(MEMBER_CARD_ID, CARD_NO, USER_ID, REAL_NAME, CARD_TYPE_NAME, CARD_NAME, CARD_ID, `EFFECTIVE`, `EXPIRED`, EXPIRED_VALUE, EXPIRED_UNIT, INITIAL_BALANCE, AVAIL_BALANCE, `REMARK`, `STATE`, LATEST_ACTIVATE_DAY, FROM_MEMBER_CARD_ID)values('148484250576441344', 'F05', '144902165450399744', '冯小雨', '储值卡', '100元无折扣', '147018595541049344', '2018-05-08T00:00:00.000+08:00', '2018-06-08T00:00:00.000+08:00', 1, '月', 100.0, 100.0, NULL, '已激活', NULL, NULL)
2018-05-08 10:48:07.894 [http-nio-7689-exec-9] DEBUG tcode[19000001] eql.cn.raiyee.liyun.service.back.dao.MemberCardDaoImpl.eql.addMemberCard.result:14 - 1
图:2018-05-08 00:00:00的时间存储后,偏移到了2018-05-07 11:00:00,发生时光倒流。
我们之前同样使用阿里云的RDS,而且还是同一个实例,也同样使用eql来访问数据库,没有出现问题。怀疑是mysql驱动升级了的原因。
马上使用mvn dependency:tree分析mysql依赖,发现mysql版本确实不一样。切换会5.1.42的版本,问题解决。
理论上,mysql-connector-java:jar:6.0.3的版本,应该兼容低版本才是。可能是某些参数需要调整。所以,继续切回6.0.3版本,尝试添加时区的参数设置。只要添加serverTimezone=Asia/Shanghai就可以解决问题了。其中一篇博客mysql-connector-java 6.x 时区设置讲得很详细。
图:使用谷歌搜索相关文章
url=jdbc:mysql://test.go.easy-hi.com:33306/?useUnicode=true&&characterEncoding=UTF-8&connectTimeout=30000&socketTimeout=30000&autoReconnect=true&serverTimezone=Asia/Shanghai
为了保持线上配置不动,最终权衡后,还是切换5版本驱动。
看了一篇微信文章推送,《请问你为什么学习LISP》。之前也看过左耳朵耗子:什么是函数式编程?。什么“没有状态就没有伤害”,“并行执行无伤害”,“Copy-Paste 重构代码无伤害”,“函数的执行没有顺序上的问题”,“MapReduce”等卖点,都没有真正打动我。直到看到一句话:学会了Lisp之后你有很大概率开启上帝视角,能站在一个相当高的高度去审视别的语言,这不是在吹牛,这是真的真的相当高。
妈蛋蛋,就冲着这“上帝视角”,我准备“入坑”了。
今天随便翻了一下《阿里巴巴JAVA开发规范》,看到单元测试中提到了“AIR”原则,而我之前了解的是“FIRST”原则,不管什么原则都念叨一下总是有所裨益的,我都在这里归集一下吧。
好的单元测试必须遵守 AIR 原则。
单元测试在线上运行时,感觉像空气 (AIR) 一样并不存在,但在测试质量的保障上,
却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。
保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。 反例:method2需要依赖method1的执行,将执行结果作为method2的输入。
单元测试是可以重复执行的,不能受到外界环境的影响。 说明:单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。 正例:为了不受外界环境影响,要求设计代码时就把SUT的依赖改成注入,在测试时用spring 这样的DI框架注入一个本地(内存)实现或者Mock实现。
测试如果跑得不够快,就不会让人想常常跑,不常跑的测试最后也就失去的它意义了。
所以实务上会使用mock工具,mock其他依赖的物件或环境,来加速测试的执行。
测试要相互独立,一个测试不会依赖其他测试,如果互相依赖的话,一个测试的失败会影响其他测试也跟着失败,那么在找问题点的时候将会变得更困难。
测试应该要可以在任何环境中重复执行。减少因环境因素而产生测试失败的问题。
测试程式应该要输出布林值。不管是测试成功或失败。
简单来说就是可以在测试报告很清楚的看到红灯(测试失败)或绿灯(测试成功)。
撰写测试要及时,最好是在写产品程式前先写(TDD的概念)。
编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。
为了提高开发人员的代码质量,编写高质量的单元测试,要遵守3R(Responsible, Reliable, Repeative)原则,具体含义如下:
开发在完成一个方法,或者一个类之后,就要及时得进行单元测试;不能在对应方法或类的调用处进行测试,比如两个模块A、B,A是基础模块,为模块B提供服务,那么所有A模块的单元测试case都应该在A模块的内部进行测试。
为了使得测试用例尽量可靠,就要减少mock的使用(对于第三方的调用可以使用mock),对每层代码的测试都要完全依赖于下层,不能mock下层逻辑。因此引入递进集成的概念,比如测试DAO时要连接真实的数据库,测试Service时要使用真实的DAO、DB, 测试Controller层的代码,要使用真实的Service、DAO、DB,以此类推。这样就可以最大限度的提高case的可靠性。
必须要做到case间完全解耦,没有任何的依赖,这包括和数据库的依赖以及第三方的依赖。case解耦可以通过准备测试数据、mock第三方调用来解决。
3A原则原本是单元测试用例编写时应该遵循的基本原则
arrange 初始化测试数据,就是造数据,这里的数据有我们输入的数据,也有目标接口所涉及的资源,比如hr系统中的用户信息,我们必须先有几条人员的详细信息才能去测获取人员信息的接口(当然只是正常的流程,我们有时候还需要清掉数据以便测试资源为空的情况);
act 调用接口,传入输入数据;
assert 断言, 对返回的资源信息进行断言,比如获取用户信息的接口返回了用户信息之后,我们要判断返回的用户是不是我们想要的那个用户,我们获取的是李雷的信息,接口如果返回韩梅梅,那么接口的逻辑就是不对的;
最近一个月内多次接到用户报告,说会员在登录页面上,显示的是另外一家瑜伽馆的名字和图片,也有在订课时发生类似情况,我们称之为“串馆”。
图:2017年10月25日报告,登录时,串馆情况
图:10月23日报告,从拉姆瑜伽公众号进入的,馆名是优美甜缘的,课表是蝉悦瑜伽的,这三家都是付了费的,都是南方的。
之前由于串馆问题只发生在南方,因为南方商户较多,并发冲突的可能性更大。但是之前从日志中没有找到相关的问题,有可能是商户多日志量太大了。所以把怀疑的重点放在了Nginx根据TCODE查找TID的LUA脚本上,以及JAVA端的多租户连接池上。
LUA的一个排查重点,就是没有加local关键字的全局变量,JAVA多租户连接池的排查重点是多线程取连接问题。所有代码都走过一遍后,仍然没有发现有效线索。业务代码涉及TCODE代码很少,也没有看出可能的问题。
因为一直没有报告北方商户出现串馆问题,所以也怀疑了是不是可能Nginx本身的BUG,然后对比了南北的Nginx的版本号,发现确实不一样。北方openresty/1.11.2.1南方是openresty/1.9.15.1,本想将南方的Nginx升级成北方一样的版本号,看看是否仍然有串馆问题。
今天突然北方也报告了串馆,立马跟踪日志,很快张存鑫同学从日志中看到了一个问题时间点(12:50)的异常,然后进行关联跟踪分析之后,果然发现串馆的问题应该就是这个异常导致的。
问题分析清楚后,只需要再仔细思考一下这段代码逻辑,直接重构了,删除了一些无用代码后,改成以下这样,然后上线,经过一段时间的观察后,确认困扰了将近半个月的偶发性串馆问题自此修复。
问题总结:对于商户的路由逻辑,应该由Nginx统一通过HTTP头传递给YOGA应用,YOGA应用的过滤器中,应该从HTTP头中取出商户路由并设置到本地线程变量,避免使用会话保存等其他形式。另外,应该避免因为异常导致商户路由设置代码被跳过。
刚刚看到一篇文章,讲运维的,开篇就是这么一张图,然后配了文字是“服务最好成双成对”,哈哈,这两个养眼美女,我会盯上看一会,如果走了其中一个美女呢,我依然会盯上看几眼,但是美女走光了呢,靠,没得看了,我也只能走了。
如果没有成双成对呢,那就是单身狗了,技术上叫做SPOF(Single Point of Failure)。SPOF就是独苗,就是单身汪,挂了就没了。
想到以前我们上初中时(92年左右)的一个情景,那会学校里面没有自来水,吃水都是靠一口井来解决。然后大热天,实在太热了,大家想到一个办法,用吊水的盐水瓶系上长长的绳子,趴在井口上去吊凉凉的井水喝。喝的实在太爽,很快几个男孩子都去这么干。可是很危险啊,学校肯定不允许啊,万一掉到井里面去了咋办,那口井感觉很深的样子。所以大家都是偷偷地去吊井水,还是有一次被校长发现了。校长喝令几个男生站成一排,很严厉地再次讲了这么做的危险。然后一个一个地问,你家几个娃,哦,两个男娃,你走吧,你家呢,啊,就一个独苗,对不起,这时候校长就往后退半步,两脚稍微分开一些,身子稍微下层一点,伸出双手,合在一起吹吹神仙气,然后张开手臂,运起力气,吧唧一声,一记响亮的夹耳光狠狠地打了下去,然后被打的独苗虽然脸被打得红红的,也狠狠地憋住不哭,但是也禁不住已经两眼泪水汪汪了。那可是令人恐惧,令人印象深刻的校长打人专利-夹耳光。
所以,从初中我就知道,不是独苗还有这好处,因为我有一个哥哥,所以就免于因为吊井水而被夹耳光的痛苦(虽然还有其他时候被夹耳光)。
回到技术上,服务成双成对,是最基本的生产环境的集群配置了。当然,为了配合这个成双成对,还得引入前面的负载均衡技术,引入VIP虚拟IP技术。成双成对仅仅是最初的开始。
【阿里云】华东2(上海)的云数据库RDS版实例ali-hd2-hi-rds-t01,磁盘使用率平均值超过80,请登录云监控关注
【阿里云】华北2(北京)的云数据库RDS版实例ali-hb2-hi-rds-t01,磁盘使用率平均值超过80,请登录云监控关注
持续收到了一周了,看来得进行门户清理了。
drop table databasechangelog;
drop table databasechangeloglock;
-- tt_d_funcright和tt_f_role_right商户库不需要,中心库需要,注意只删除商户库。
drop table tt_d_funcright;
drop table tt_f_role_right;
drop table tt_d_wx_msg_template;
drop table tt_l_mbrcard_times;
drop table tr_f_db_sp;
drop table ts_f_stat;
truncate table tt_f_file;
drop table tt_f_schedule_rule_bak01;
drop table tt_f_schedule_rule_bak02;
drop table tt_l_mbrcard_chg_bak;
drop table tt_f_card_rule3;
drop table tt_f_card_rule4;
drop table tt_f_wx_msg_article;
drop table tt_d_wx_msg_handler;
-- 查询行数大于1万的表
SELECT TABLE_SCHEMA,TABLE_NAME,TABLE_ROWS,TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA not in ( 'information_schema', 'mysql', 'performance_schema' ) and table_rows > 10000 order by table_rows desc
delete from tt_f_schedule_rule where schedule_id in (select schedule_id from tt_f_schedule where start_time < '2018-01-01 00:00:00');
delete from tt_l_remark where operate_time < '2018-01-01 00:00:00';
delete FROM tt_f_card_rule where state = 0;
心惊胆战的drop表,胆战心惊的drop库(包括手工drop,程序drop),干了一天下来,看下效果,磁盘空间还是降了一些的。
图:北方RDS磁盘空间
图:南方RDS磁盘空间
为了降低对Redis的频繁读取,也提升效率(包含序列化/反序列化),我打算在系统中实现二级缓存,L1用Guava Cache来做,L2用Redis来做(为了同步),然后翻阅了一些文章,从Latency Numbers Every Programmer Should Know发现如下数据,暂且参考记录一下。
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns
Credit
------
By Jeff Dean: http://research.google.com/people/jeff/
Originally by Peter Norvig: http://norvig.com/21-days.html#answers
Contributions
-------------
'Humanized' comparison: https://gist.github.com/hellerbarde/2843375
Visual comparison chart: http://i.imgur.com/k0t1e.png
引用玉伯的话来说:博客就三个关键词:记录、传播、对话。能满足这三个需求的系统,就是博客。
于是乎,正式打算利用github的issue来正式写博客了。虽然从毕业到现在已经好些年了,写博客的习惯一直没有很好的养成,但是一直未放弃,一直是念念不忘。带团队的过程中,我也鼓励小伙伴们把一些感想,一些发现问题分析问题总结问题的收获,都及时写下来,后面可以回味,可以分享,可以大总结。
图:性能优化在路上
业务都还没有理顺,流程还没有抽象,就考虑优化,有点过早。
把业务进一步精简,去除伪需求。
也就是消除同步,去除阻塞。
也就是Lock Free。使用CAS,Ringbuffer等。
池化一些资源(线程池、连接池、XX池)
使用ByteBuffer等,例如Netty。
参考文章:走进科学之揭开神秘的"零拷贝"
一个个处理,费时费力。一批处理时,可以尝试合并,压缩等。
图片来源于500px
即“使用纯文本来存储数据”。为什么呢?因为这样可以使数据具有最大的可移植性。
在使用Apache POI来生成Excel时,当修改Excel表单的名字时,导致保存后的Excel打开,图标数据丢失,打开报告错误:
原因找到了,可是怎么去修复呢,完全不知道POI图表的哪个API能设置这个啊。这时候连万能的谷歌都不好使了,毕竟这个是非常小众的场景啊。
简单尝试了去修正图标对表单名的引用,但是因为找不到正确的API,都无功而返。只好先把精力放在业务相关上,暂时不允许表单改名了。
周末在家里想着,XLSX不就是OOXML格式么,其实就是一个普通的zip格式啊,完全可以解压开来,然后去看看图表数据中对于表单名的引用是存储在哪里的。
用在线XML工具分析一下chart1.xml的文档结构,如下:
发现文档名的位置在:chart->plotArea->barChart->ser->val->numRef->f下面。然后就顺藤摸瓜,很快找到了对应的API了,成功完成表单名的改名动作。
/**
* 修正图表中对于表单名字的引用。
*
* @param sheet EXCEL表单。
* @param oldSheetName 旧的表单名字。
* @param newSheetName 新的表单名字。
*/
public static void fixChartSheetNameRef(Sheet sheet, String oldSheetName, String newSheetName) {
val drawing = sheet.getDrawingPatriarch();
if (!(drawing instanceof XSSFDrawing)) return;
for (val chart : ((XSSFDrawing) drawing).getCharts()) {
for (val barChart : chart.getCTChart().getPlotArea().getBarChartList()) {
for (val ser : barChart.getSerList()) {
val val = ser.getVal();
if (val == null) continue;
val numRef = val.getNumRef();
if (numRef == null) continue;
val f = numRef.getF();
if (f == null) continue;
if (f.contains(oldSheetName)) {
numRef.setF(f.replace(oldSheetName, newSheetName));
}
}
}
}
}
因为刚刚回看了Linux/Unix设计**,想到XML其实也是FLAT原则的一种体现。如果不是OOXML的格式,而是以前的xls,那么就没那么容易肉眼去分析格式,去寻找对应的API了。感谢FLAT,感谢纯文本。
2018年02月07日凌晨,SpaceX旗下的新型火箭,也是现役运力最强火箭“重型猎鹰”(Falcon Heavy)首飞成功。
"重型猎鹰"火箭有多厉害,看看他打破了多项世界纪录:近地轨道运载能力(LEO)63.8 吨,比目前纪录保持者德尔塔 IV 重型火箭的 28.8 吨提高了两倍多;地球同步轨道运载能力(GTO)26.7 吨;火星轨道的运载能力16.8 吨。"重型猎鹰"火箭起飞时 27 (9*3)台梅林 1D 发动机同时工作,可以提供高达 2280 多吨的起飞推力。
这一天,三联生活周刊的公众号也推送它的新文章:SpaceX再创历史|埃隆·马斯克:无限创想与意志胜利,里面提到:
马斯克也开始诠释打上他个人烙印的硅谷极客精神,叫思维的“第一性原理”(First principle thinking),也就是打破一切知识的藩篱,回归到事物本源去思考基础性的问题——这与他的物理学出身又密不可分。
然后我就被这个“第一性原理”的字眼吸引住了,然后开始了一系列的谷歌搜索。
我们运用第一性原理,而不是比较思维去思考问题是非常重要的。我们在生活中总是倾向于比较,对别人已经做过或者正在做的事情我们也都去做,这样发展的结果只能产生细小的迭代发展。
第一性原理的**方式是用物理学的角度看待世界,也就是说一层层拨开事物表象,看到里面的本质,再从本质一层层往上走。
媒体在采访 Elon Musk 时,提到的一个运用第一性原理,发现商业机会的例子是这样的
传统电池组,市场平均价格是600美元/千瓦时,主要电池供应商是松下。马斯克通过第一性原理发现,如果从伦敦金属交易所购买锂电池组的原材料组合在一起,只需要80美元/千瓦时。
他从中发现有巨大的价格差距,所以特斯拉在2013年开始自己建立了电池厂,今年一月份开始大规模生产,投产之后电池的价格可以下降30%,每年可以支持150万辆电动车对电池的需求。这就是他对第一性原理的一个应用。
与其根据参照物去推论,不如我们把问题分解成几个最基本的事实,然后检查每个事实部分。即使问题已经解决,我们还是要从问题最基本的组成部分入手,重新审视是否有更好的解决方案。 这就是马斯克一直推崇的用第一性原理思考问题,而不是类比。
注:图片来自blogspot的一篇博客
摘自维基百科:伊隆·马斯克(英语名:Elon Musk,1971年6月28日-)是一名美籍和加籍企业家。他于南非出生。因他为SpaceX的创办者,及特斯拉汽车和PayPal(原X.com)的联合创办人而闻名。目前,马斯克担任Space X的首席执行官兼首席设计师、特斯拉汽车首席执行官兼产品架构师、以及SolarCity的主席;与此同时,他还是现代第一辆可行电动车Tesla Roadster的联合设计者之一。
摘自一篇简书
加拿大华人老喻总结的一个公式 —— 为一切问题提供广泛可行的解决方案:
你的成就=核心算法×大量重复动作²
所谓“核心算法”,就是你的“第一性原理”。就是你始终揪住它不放松的东西,做任何事都是使用这个“核心算法”,在任何选择关头,不管别人怎么说,怎么看,都用这个原理做决策。
所谓“大量重复的动作”,就是一旦启动开始重复地做,笨笨地坚持往下做。每多做一次,就会比其他人积累更大的优势,而且这个优势是指数级积累的。
还是那几个概念:刻意练习一万小时定律;七年就是一辈子;一次只做一件事;把运气交给上天自己只管练就一颗坚强的心。
这些道理在今天已经成为普世的精进理论,但如果核心算法是飘忽不定的,那再多大量重复的动作,也难以换来自己期望的成就。揪住核心算法不放,这一点比什么都重要。
摘自思维浩的解释
“第一性原理”是一个量子力学中的一个术语,意思是从头算,只采用最基本的事实,然后根据事实推论。
在MUSK开发电动车案例中,有些人会说现在的电池组真的很贵,而且未来价格也不会低到哪去,因为它过去一直都是那么贵。这些人会说电池组每千瓦小时要烧掉600美元。而且未来也不会好到哪里去的。有史以来,成百上千的权威人士都声称一个行业、设计图样、一个实体或者一个想法都已经达到了它的顶峰。在这种思路的影响下,电池组再也没有改进的空间,或是以更低的成本生产出来。这些人说的话都被汹涌扑来的创新证明是无稽之谈。或者,他们可以拍屁股想出一些微不足道的改进。”
然而从第一性原理出发,我们问:电池的材料构成都是什么?这些材料的现货市场价值是怎样的?电池是由碳、镍、铝、其他用于分离的聚合物还有一个金属罐组成的。如果我们去伦敦金属交易所购买这些金属材料,然后把这些材料分解一下,那么这些组成电池组的材料每种又值多少钱呢? Musk在采访中说道,他将电池组分解成最基础的材料组成部分:碳、镍、铝、其他用于分离的聚合物还有一个盒子。这些都是电池组重要的组成元素,这是形成一块电池的最基本的事实。从那里,每个部分都可以优化、改进,最终的优化程度也取决于解决问题这些人的聪明才智。于是,现在电池的价格就变成了每千瓦小时80美元。
与其根据参照物去推论,我们应该把问题分解成几个最基础的事实,然后检查每个事实部分。即使问题已经解决,我们还是要从问题最基本的组成部分入手,从新审视是否有更好的、可能的解决方案。
这就是MUSK一直推崇的用第一性原理思考问题,而不是类比。
摘自梦之轮回
据说这是,来源于“第一推动力”这个宗教词汇。
第一推动力是牛顿创立的,因为牛顿第一定律说明了物质在不受外力的作用下保持静止或匀速直线运动。如果宇宙诞生之初万事万物应该是静止的,后来却都在运动,是怎么动起来的呢?牛顿相信这是由于上帝推了一把,并且牛顿晚年致力于神学研究。
现代科学认为宇宙起源于大爆炸,那么大爆炸也是有原因的吧。所有这些说不清的东西,都归结为宇宙“第一推动力”问题,它可能由某种原理决定,这个原理可以称为“第一原理”。
爱因斯坦晚年致力于“大统一场理论”研究,也是希望找到统概一切物理定律的“第一原理”,可惜,这是当时科学水平所不能及的。现在也远没有答案。
但是为什么称量子力学计算为第一性原理计算?大概是因为这种计算能够从根本上计算出来分子结构和物质的性质,这样的理论很接近于反映宇宙本质的原理,就称为第一性原理了。
打个比方,在人文领域中,一个国家,其出发点一定是每个公民都觉得不言而喻的公理,科学的方法可用于治国,让建国也基于最简单的公理,公民能理解,国家才会牢固。
简单的说,再复杂的知识体系,一定要归纳成最简单的几条不言而喻的公里,这就是第一性原理。
摘自不要拦我的回答
商业角度总结如下:
我们不妨从它的定义开始。
“第一性原理”是物理学的一个专业名词,是指某些硬性规定或者由此推演得出的结论。与之相对的则是“经验参数”,经验参数是通过大量实例得出的规律性的结论。(来自百度百科)
简单来说,第一性原理就是指一个定理,或者定理的推论。那么,一个物理学上的概念,为什么会被埃隆·马斯克如此推崇?又是如何被运用到商业中呢?仔细研究下定义,你会发现“第一性原理”和与之相对的“经验参数”,其实是人类的两种思维方式——演绎法与归纳法。
“第一性原理”是一种演绎法思维,由1个或多个定律推演而来,或者它本身就是一个定律;
“经验参数”是一种归纳法思维,由N个已知的数据或现象,推论出一个规律。
两者有何区别呢?举个例子说明,船长观察到前方有座冰山:
一个善用归纳法思维的船长A会这么想:我上次遇到的冰山在水面下还有一个大冰山,我上上次遇到的冰山也是这样,泰坦尼克号遇到的冰山也是一样的,所以这个冰山下面一定也会有一个大冰山,看来我要小心地绕过去。
一个善用演绎法思维的船长B会这么想:观察到这个冰山是移动的,说明它是浮在水中。根据浮力原理,F浮=G排(即物体浮力等于物体下沉时排开液体的重力),所以水面下一定藏有一个体积足够大的冰山,看来我要小心地绕过去。
虽然殊途同归,但是很明显这是两种完全不同的思维方式。(这句话很重要,后面我还会提到)
人们总是更习惯于用归纳法思维来总结规律和解决问题。比如:
员工A销售业绩突出,电话量很高;员工B销售业绩突出,电话量同样很高;……
所以得出结论,电话量是影响销售业绩的一个重要指标,于是KPI加上了电话量的考核。
这是我们工作中很常见的一种现象。但并不是**教育和文化所带来的特有现象,而是全人类都更倾向于使用归纳法。
如果用演绎法,又会如何思考这个销量问题呢?
销售是什么?
是让产品与客户接触,激发出客户购买欲望,并且实现成交的一个过程。
由此可见,销售分为3个阶段:
step 1. 让客户接触到产品→step 2.激发出购买欲望→step 3.成交过程
其中step 1又可以拆分成官网展示、电话推销、样品寄送;同样step 2也可以拆分为官网广告文案、电话推销术语等环节。
因此,如果是以电话销售为主的产品,想要提高step 1和step 2的产出,可以增加电话量。(注意,这个时候的演绎法的结论与归纳法思维再次殊途同归。标记为结论一。)
但是,换个角度的话,你还能发现,为什么一定要用电话销售的形式呢?step 1和step 2是否可以变成新媒体营销,是否可以变成电商营销,是否可以变成网红营销?(此时,演绎法与归纳法的区别就出现了。标记为结论二。)
看到这里,你应该知道了归纳法与演绎法这两种思维的不同之处。
归纳法只能对已发生的事实总结规律,常常会忽略尚未在内部发生的新生事物。而颠覆式创新却是一种未发生的事情,所以归纳法思维是很难创造出颠覆式产品的。
自从埃隆·马斯克提出第一性原理之后,互联网圈里立刻流传开来,网上有很多文章讲解“第一性原理”到底是什么。我看到很多人这样诠释:
第一性原理思维是一种“追本溯源”的思考方式,万事都要寻找到根本性问题,也可以叫本质思考法。
然后读者们马上看懂了,新瓶装旧酒嘛,工作中已经都在用了。我还看到丰田公司有位员工在某篇文章底下评论,“我们一直在这样做,制造业上叫根因法。”意思是说,这个大家早就知道了,根本不是埃隆·马斯克的独门秘籍。
但是很可惜,这个解释其实是不对的,误导了很多人。
第一性原理思维≠追本溯源、根因法、本质思考法
回想下之前提到的两个例子,冰山案例与销售案例。从中我们能发现两条重要结论:
第一条,归纳法与演绎法有时会出现一致的结论(参考:冰山的结论与电话量的结论一)。这说明,同样的结论,不一定思维方式是一样的。
第二条,使用演绎法出现了两个结论(参考:电话量的结论一和结论二)。这说明,同样的思维方式,不同的思考深度和角度可能会带来不同的结论。
根据这两个重要结论,我们会有些新发现——
也就是说,第一性原理思维是演绎法思维的一种,追本溯源、根因法也是演绎法思维的一种,但两者不是同一个。并且如果两者的思考角度不同,就可能会带来不同的结论。
这就是为什么跟埃隆·马斯克一样有深度有广度的商业大咖们,没能像埃隆·马斯克一样。即使他们都是善用演绎法思维的人,也是更习惯于使用“追本溯源”演绎法。第一性原理思维这种演绎法思维只有极少数人知道。
(这里插入一句,并不是思考角度不同都会带来结论差异。大家之所以弄混第一性原理思维和追本溯源思维,恰恰是因为这两者的结论经常相同,才会误以为两个是同一种思维。)
那么,这两种演绎法,有何不同呢?用鱼骨图能很直观的看出区别。
追本溯源法是从问题出发,一步步分析问题背后的原因,直到找出最终原因(有1个或者N个)。
第一性原理思维法则是从原理出发,一步步往前推演,直到找出适合该问题的解决方法(有1个或者N个)。
由此可见,第一性原理思维和追本溯源思维是不同的,一个是从问题出发,推演出根本原因;一个是从原理出发,推演出解决方法。
我们再仔细分析下,为何出发点不同,会带来差异性的结果。
如果用追本溯源思维,从问题出发,那么它能一步步发现子路径1→路径1→第一性原理;但是它很难发现路径2、路径3、路径4,因为这种思维方式是要从问题开始推演的。而新路径(创新)恰恰隐藏在路径2、路径3、路径4之中。这就是用第一性原理思维常常能带来颠覆式创新的根本原因!
这才是埃隆·马斯克极度推崇第一性原理思维的真正原因。
如果看过《创新者的窘境》这本书的话,可能对这句话的感触会更深一点,因为能更清楚企业创新的重要性。
再看看上面那张图,聪明的朋友可能会发现一个问题,那就是第一性原理思维的局限性。
任何一个原理或定律,都不可能解决世界上所有的问题。因此,如果出问题的地方不在你的第一性原理的体系中,那么采用第一性原理的思维方法,是找不到解决方案的。
怎么办呢?
第一性原理是一个定律,或是一个模型。搜集的越多,那你能解答的问题也就越多。
巴菲特最重要的伙伴查理·芒格就曾说过,他热爱学习,尤其是跨学科学习,通过这种方式他搜集了100多种思维模型,他就是用这些模型来制定投资策略的。
感谢芒格给我们指出了一条捷径,培养第一性原理思维的捷径就是跨学科学习。
不要只看商业书籍,其实每个学科里都散落着大智慧,物理学的力学定律、生物学的进化论、经济学的看不见得手……很多看似与商业不相干的定律,都是很好的第一性原理,认真反复推演之后,一定能发现适用于自己的思维模型。
突然看到一段代码,看完就是那种感觉,闭上眼睛,眼前就一行行文字在飞奔,很Happy地在飞奔。
@Override public void run(int periodSeconds) {
log.info("->->->->->飞奔的定时任务->->->->->飞奔的定时任务->->->->->飞奔的定时任务->->->->->飞奔的定时任务->->->->->");
val now = DateTime.now();
val targetTime = now.minusSeconds(periodSeconds);
if (now.getHourOfDay() == targetTime.getHourOfDay()) {
return; // 每小时执行
}
log.info("->->->->->钻研的定时任务->->->->->钻研的定时任务->->->->->钻研的定时任务->->->->->钻研的定时任务->->->->->");
val memberCards = memberCardLatestActivateDayDao.queryUnactiveMemberCardsWithLastedActivateDay();
for (val memberCard : memberCards) {
tryActivateMemberCard(memberCard);
}
}
写这个代码的不一定是高级程序员,但我想一定是一位快乐的码农。这个有点让我想起"Go语言趣味教材:并发不是并行"中囊地鼠gopher很Happy地运书的一张图片:
囊地鼠运书,运得很快乐啊。也感觉像一枚枚成功飞向太空的小火箭(猎鹰重型首射的有效载荷是代号为Starman的红色特斯拉跑车Tesla Roadster),也飞得很Happy。
GIT看一下这段代码的提交记录,还是热热乎乎的代码,提交注释是test
。
于是我就问原始作者,
问:小雨,你打算什么时候去掉它?
答:我不准备去掉它,留着很好啊。
问:五段重复的话,中间大量的箭头字符,留着有什么意义?
答:打日志的时候会看得很清楚啊。
问:万一大家也都跟风,也都这么的打印,那你还能从日志中看定时任务看得很清楚呢?
答:……
也许我担忧过头了,只要诸多打日志的代码中,有且仅有这么一处这么打印的话,问题倒也不大,而且还感觉有『某种新意』。但是,经常是『好事不出门,坏事传千里』,这毕竟不是一个好的打印日志的方式,开启了这么一个方便之门后,后面其他人活着新来的同学也都这么打印,就会导致满屏幕火箭飞了,到底是飞的定时任务的小火箭,还是飞的其它业务的火箭,就不是那么容易区分了。而且,大概率还会嘟嘟说,组长就是这么写代码的,那就不好了。
明显“飞奔的定时任务”属于调试性的日志,可以用debug级别。另外,靠肉眼去看日志,只适合在开发环境,在生产环境大量访问情况下,这种打印日志的方式,只会造成日志大小的无意义的增长。
如果不删除,至少所以建议的改动如下:
@Override public void run(int periodSeconds) {
log.debug("定时任务检查开始");
val now = DateTime.now();
val targetTime = now.minusSeconds(periodSeconds);
if (now.getHourOfDay() == targetTime.getHourOfDay()) {
return; // 每小时执行
}
log.debug("定时任务执行开始");
val memberCards = memberCardLatestActivateDayDao.queryUnactiveMemberCardsWithLastedActivateDay();
for (val memberCard : memberCards) {
tryActivateMemberCard(memberCard);
}
}
改了:
这两天我升级了缓存类库westcache和SQL类库eql,以使得二者更加方便的集成,比如以下写法:
@EqlerConfig
public interface DictDao {
@WestCacheable // westcache注解
@Sql("select id, name, addr from cache_dict") // Eql注解
List<CacheDictBean> selectAll();
}
我很快就把二者集成的实现写好了,但是,发现应用启动由原来的大概50秒,慢吞吞地变成了5分多钟,OMG,要疯了,这还受得了。
很快,我就定位到了问题,原来是westcache里面的有一处实现,利用了异常NoSuchMethodException来查找方法是否在一个class中存在,我意识到这很可能是一个问题,因为我早就耳闻异常造成的性能低下。我尝试将它改造成不使用异常的方式来实现,改完后,再测试,应用启动时间就马上降下来了,恢复到原来差不多的时间。
于是,我单独写了一个基准测试来模拟一下两种情况,形成一个比对,运行结果如下:
Benchmark Mode Samples Score Error Units
o.n.s.s.MethodSearchWaysCompare.methodNotFoundException thrpt 10 470604.189 ± 80316.210 ops/s
o.n.s.s.MethodSearchWaysCompare.methodsSearch thrpt 10 1295466.093 ± 34110.967 ops/s
可以看到,不使用异常的吞吐量比使用异常的快一个数量级。可见,异常在循环中,或者高并发处理中,还是要慎用,否则很可能就成了性能杀手。
X年X月X日,项目X中:
小明:师傅,用户表中的性别字段1和0,哪个表示男哪个表示女呢?
师傅:1表示女,0表示男。
新人:哦,知道了。(思索片刻,然后顿悟了,原来组内的女生都亭亭玉立,男生大多大腹便便。)
Y年Y月Y日,项目Y中:
小明:组长,客户表中的性别字段1和0,哪个表示男哪个是女呢?
组长:1表示男,0表示女。
小明:了解了。(发呆片刻,然后理解了,原来1是带把的,0是不带把的。)
Z年Z月Z日,项目Z中:
小明:兵哥,联系人表中的性别字段M和F,哪个表示男哪个表示女啊?
兵哥:Male和Female啦。
小明:哦,原来是英语单词首字母啊。
S年S月S日,项目S中:
小明:咦,这个项目中的性别,竟然直接就用“男”和"女"表示,终于不用问人了。
-- [getEarningsDetailLogList]
SELECT U.NICKNAME AS 'memberName',
U.MOBILE AS 'memberMobile',
mc.MBR_CARD_ID,
c.CARD_NAME,
mc.CHANGE AS 'changeLog',
(CASE WHEN mc.LOG_TYPE = '105' THEN '续卡'
WHEN mc.LOG_TYPE = '112' THEN '作废卡'
ELSE '开卡'
END) AS 'logType',
mc.PAY_TYPE,
DATE_FORMAT(mc.OPERATOR_TIME, '%Y-%m-%d') AS 'operatorTime',
DATE_FORMAT(mc.OPERATOR_TIME, '%Y-%m') AS 'operatorTimeSub',
CASE WHEN c.STORED_MONEY IS NULL THEN FALSE ELSE TRUE END AS 'valueCard',
CASE WHEN mc.PREFERENTIAL_WAY = '0' THEN '优惠券减免'
WHEN mc.PREFERENTIAL_WAY = '1' THEN '特价优惠'
WHEN mc.PREFERENTIAL_WAY = '2' THEN '赠送'
WHEN mc.PREFERENTIAL_WAY = '3' THEN '活动优惠'
ELSE '' END AS 'preferentialWay'
FROM TT_L_MBRCARD_CHG mc, TT_F_MBR_CARD m, TT_F_CARD c, TT_F_USER U, TT_F_MEMBER me
WHERE mc.MBR_CARD_ID = m.MBR_CARD_ID
AND m.CARD_ID = c.CARD_ID
AND U.USER_ID = me.USER_ID
AND m.MEMBER_ID = me.MEMBER_ID
AND mc.LOG_TYPE IN ('105' , '111' ,'112')
SELECT O.ORDER_ID,
O.ORDER_NO,
TO_CHAR(O.CREATE_TIME,'YYYY-MM-DD HH24:mi:ss') CREATETIME,
DECODE(O.PAY_TYPE,'0','在线支付','1','货到付款','2','自提支付','3','公司转帐','4','邮局汇款') PAYTYPE,
TO_CHAR(NVL(O.INCOME_MONEY,0)/1000,'FM9999999990.00') INCOMEMONEY,
TO_CHAR(NVL(O.POST_FEE,0)/1000,'FM9999999990.00') POST_FEE,
O.POST_TAG,
DECODE(O.ORDER_STATE,'00','未处理','01','待分配','02','订单补录','03','待发货','04','发货中','05','物流在途','06','成功关闭') ORDERSTATESTR,
T.GOODS_ID,
TO_CHAR(NVL(T.AMOUNT_RECEVABLE,0)/1000,'FM9999999990.00') PRICE,
T.SALE_QUANTITY,
TO_CHAR(NVL(T.AMOUNT_RECEIVED,0)/1000,'FM9999999990.00') AMOUNT_RECEIVED,
T.GOODS_NAME,
P.POST_CODE,
P.POST_ADDR,
P.MOBILE_PHONE
FROM TF_B_ORDER O, TF_B_ORDER_GOODS T, TF_B_ORDER_POST P
WHERE T.ORDER_ID = O.ORDER_ID
AND P.ORDER_ID = O.ORDER_ID
AND O.ORDER_ID = #ORDER_ID#
SELECT O.ORDER_ID, O.ORDER_STATE, D.STATE_NAME
FROM T_ORDER O LEFT JOIN ON T_ORDER_STATE_DICT D
ON O.ORDER_STATE = D.ORDER_STATE
2017年X月X日,办公室中,新版本升级正在如火如荼的进行中。
KING哥(资深DBA): 老黄,怎么现在状态字段都是中文了,什么“排队中”、“排队成功”、“排队失败”,为什么不用编码1,2,3呢,你确定这样使用中文不会有字符集问题,不会浪费存储空间嘛。
老黄(资深码农):KING哥,我现在感觉自己老了,记忆力不如以前了,所以不用1,2,3等之类的编码了,因为每次我都要去查编码说明或者询问小雨,浪费时间太多,所以就直接用中文了。另外中文字符集问题,现在都什么年代了,我对中文表示很有信心(文化自信)。存储空间嘛,我们就这点数据量,完全不在话下。
同年Y月Y日,给一新人小禹讲技术。
小禹:兵哥,现在编码都提倡用直接的中文短语了,我这个新设计的存储表格里,是否启用这个字段,我也设计成了字符类型,注释里面写上:“是否启用,是/否”啊,这样就与你的**一致了。
兵哥:小禹,你确定就是布尔二值类型的字段么,不会有第三种值了么?
小禹:目前看来是的。
兵哥:那还是保持一贯的约定吧,使用tinyint的0和1来表示吧。
小禹:啊?(懵懂中)。
摘:Codeless Code《第85案-空空大师的中庸》
两个僧人找到空空大师解决纠纷。
僧人甲说:“我提出了一个绝对漂亮的设计,可是那个家伙说太复杂、没法维护。”
僧人乙说:“我提出了一个绝对简单的设计,可是那个家伙说太局限、不会有用。”
空空大师转向她的白板,画下一个大大的Φ。
僧人甲说:“我不明白。”
僧人乙说;“我也不懂。”
空空答道:“你们是在用二进制争论,不是零就是一。等你们学会浮点数再回来吧。”
比如以上页面需要进行业务调整,我想快速找到相关代码实现的地方,于是通过IDEA代码全文搜索,检索“取消签到”、“确认帮约”、“确认修改”等在页面上展示在一起的短语,但是经常找不到。只能退而求其次,去寻找“签到",”帮约",“修改”等字眼。在寻找”帮约"时,都失败,最后通过字眼"约“(大量结果中一个个比对寻找)才找到。
为什么找不到呢,因为我们代码中把这些短语拆零了。这些拆零的短语,使得我们直观想通过完整的短语来全文检索代码变得很不方便,只能使用更短的词语来花时间寻找,大大延长了代码定位的时间,因为更短的词语检索,带来的是更多的结果。
span(@click.stop="checkIn(subscriptionId, isCheckIn)") {{isCheckIn ? '取消':''}}签到
span 确认{{ isStaff ? '帮' : '预' }}约
button.btn.btn-theme-primary(type="submit") 确认{{cardId ? '修改' : '添加'}}
const url = `/SubscriptionList/${checkIn ? 'cancelCheckinSubscription' : 'checkinSubscription'}/${subscriptionId}`
const styleLoader = `${options.vue ? 'vue-' : ''}style-loader`
类似以上这种代码,只需要稍作调整,就可以通过完整短语来快速搜索定位到了。
span(@click.stop="checkIn(subscriptionId, isCheckIn)") {{isCheckIn ? '取消签到':'签到'}}
span {{ isStaff ? '确认帮约' : '确认预约' }}
button.btn.btn-theme-primary(type="submit") {{cardId ? '确认修改' : '确认添加'}}
const url = `${checkIn ? '/SubscriptionList/cancelCheckinSubscription' : '/SubscriptionList/checkinSubscription'}/${subscriptionId}`
const styleLoader = `${options.vue ? 'vue-style-loader' : 'style-loader'}`
代码中术语、短语虽然有时候虽然只有一两个个字的差异,但是最好不要拆散它们,尽量保持完整,这样使得短语检索,能更快地定位到代码的位置,何乐而不为呢。
保持完整、保持完整、保持完整,代码离完整、离优雅就更近了。
"奕起嗨"瑜伽经营管理互联网服务最近上线了一个"课程订满排队"的功能,方便瑜伽馆的会员在热门课程已经订满又极度想预订的情况下,加入排队,一旦有人取消预订则可以排上队。大体流程如下:
虽然看上去小小的排队功能,涉及到的改造点还挺多:预订排队、帮订排队、取消预订、取消课程、取消排队、排队设置、我的预定等。经过小伙伴们一起努力设计开发测试发布,在一个迭代后终于上线了。
看上去多美好的一个功能啊,能排队啦。这年头,热门的稀缺的,排队是常态。去政府部门国有银行办事得排长队;去火车站进站,要排短队;去领券参观辽宁号,还要通宵排队。
在我们自己沉浸在自我满足中时,客服有一天突然找过来,说有一个馆主要求关闭排队,但是关闭不了,因为目前还有人正在排队中,必须等没人排队了,才能关闭,要我们协助处理。具体原因大概是,馆主刚开始也觉得排队不错,就打开了,结果发现自家的课程,个个都是热门,个个都很快订满了,后来的就需要排队,本来是好事,但是馆主仔细想想,觉得不对劲啊,照这样子谁还办新卡啊,课程这么难预约上还要排队,影响发展新会员卖新卡啊,坚决要求关闭,说会让人产生混淆。
仔细一想,这个馆主想得好像没问题(逻辑上另论),我们也客户第一,第一时间从后台帮他关闭了排队功能。再回头想想,我们花费了力气,开发的这个排队功能,竟然被客户排斥了,那么它到底是不是鸡肋呢。
我们的开发人员也经常抱怨,产品老是闭门造车,想出一些客户很少或者基本上不用的功能,让开发去实现,然后再不停地改,有时候还反复地改,造成开发与产品之间的常态怒怼。
精益创业中,有一个概念,就是MVP(最小可行性产品),也就是快速最小成本造一个MVP,看看用户,试水市场,有人用就说明有价值,然后再跟进丰富它,没人用,那就放弃。
对于产品中的一个小功能,应该也可以有类似概念。比如排队,如果用的人多,有人提意见了,比如想在某一个课程上设置是否允许排队等,那就是好事,说明真的有人用了还有人提需求了。但是更多的可能是,真的没什么人去用它,我们能否接受这种现实,或者乐于接受这种现实而减少吐槽呢?
套一下8020原则,一个产品的核心功能在它的20%。那么我们是不是那个80%就不做了呢,应该不是,如果没有那个80%,20%也没有意义,所谓皮之不存毛将焉附,不能因为吃到第3个饼吃饱了,就说前面两个饼吃得没有意义。
我们需要的是螺旋式上升,我们希望能做的都是20%的核心功能,但是可遇不可求,只有我们把80%的基础打牢了,20%才能从其中涌现。
(图:设计一个大象🐘的滑板车,可能是一个鸡肋。)
「鸡肋」直译是chicken's ribs。不过,**人说「鸡肋」,通常有两个意思。一出自三国杨修的故事:曹操讨刘备,进退两难,给将士的命令只有「鸡肋」二字,众人大惑不解,杨修却知道曹操准备退兵,指出「夫鸡肋,食之则无所得,弃之则如可惜」(《后汉书.杨修传》)。这个「鸡肋」,英文恐怕没有同义成语。《林语堂汉英词典》译做anything insipid, uninteresting(任何燥枯无味的东西),只译出「鸡肋」的一半意思。要不失原意,译文须详细一点: something not good enough to get excited over, but not bad enough to forego without regret。Forego是「放弃」的意思。
「鸡肋」的另一意思,出自晋朝刘伶的故事:刘伶有一次醉酒触怒了别人,那人捋起双袖准备揍他,他说自己瘦弱如「鸡肋,不足以安尊拳」,逗得那人一笑息怒(《晋书.刘伶传》)。这个「鸡肋」,林语堂译做fragile(虚弱的),自然不错;假如作名词,则可译做weakling(孱弱的人),例如: A puny weakling, he was bullied by his classmates(他体如鸡肋,被同学欺凌)。
在看文章golang中goroutine的调度时,发现里面的几张图,竟然是纯文本画出来的,感觉很有意思,特此mark一下,回头也尝试尝试。
mstart
|
v 找不到可执行任务,gc STW,
+------+ 任务执行时间过长,系统阻塞等 +------+
| spin | ----------------------------> |unspin|
+------+ mstop +------+
^ |
| v
notewakeup <------------------------- notesleep
acquirep(p)
不需要使用的P P和M绑定的时候 进入系统调用 procresize()
new(p) -----+ +---------------+ +-----------+ +------------+ +----------+
| | | | | | | | |
| +------------+ +---v--------+ +---v--------+ +----v-------+ +--v---------+
+-->| _Pgcstop | | _Pidle | | _Prunning | | _Psyscall | | _Pdead |
+------^-----+ +--------^---+ +--------^---+ +------------+ +------------+
| | | | | |
+------------+ +------------+ +------------+
GC结束 releasep() 退出系统调用
P和M解绑
+------------+
ready | |
+------------------ | _Gwaiting |
| | |
| +------------+
| ^ park_m
V |
+------------+ +------------+ execute +------------+ +------------+
| | newproc | | ---------> | | goexit | |
| _Gidle | ---------> | _Grunnable | yield | _Grunning | ---------> | _Gdead |
| | | | <--------- | | | |
+------------+ +-----^------+ +------------+ +------------+
| entersyscall | ^
| V | existsyscall
| +------------+
| existsyscall | |
+------------------ | _Gsyscall |
| |
+------------+
AsciiFlow:在线流程图绘制平台是一个强大的在线ASCII图形绘制工具,ASCIIFlow是上世纪九十年代黑客们最爱的制作流程图表方式,全文本易传播,Geek 风格的反璞归真。不幸的是,目前似乎无法输入中文。
早上西游挂了,每每浏览器地址栏中输入关键字,都自动跳到谷歌搜索都无果,转而使用百度,感觉即为难受。搜索倒也罢了,问题是相关OAUTH2到谷歌账号的,也全都失效了。看来,翻墙竟成了我的日常基本需求之一了。还好,中午借来了同事的Shadowsocks账号,技术搜索的网络世界终于可以畅通无阻了,一种特别爽快的感觉。
世界上本没有翻墙,因为有了墙,所以有了梯子,而且有了造梯子的木匠。
这堵围墙,挡住的不仅仅是那么几篇看来大逆不道的网页,那只是表象,它真正挡住的,是一个人独立思考的机会。在围墙内,只有一种生意,一套文字,一个面孔,一种态度,而在围墙外,确有千百种声音,千百种态度。 有人说他们都不是真相,都是谎言。是的,也许他们都不是真相,但是围墙里的就是真相么?!古人有云:兼听则明,偏信则暗。
还好,我只搜索技术,对于其它,毫无兴趣。
有一定复杂度的密码,一般都要求以下3点:
可是,这样的密码即使构建出来了,很不容易记忆,比如Q4m)h4gWlczr!h
,很复杂吧,但是枯燥无味保准记不住。
比如:
出塞
唐·王昌龄
秦时明月汉时关,万里长征人未还。
但使龙城飞将在,不教胡马度阴山。
秦时明月汉时关,万里长征人未还。
这句就可以创造出密码Q4m)h4gWlczr!h
,输入密码的时候,只需要默念秦时明月汉时关,万里长征人未还。
以拼音首字母为基础,句首字母大写,时谐音成4,月形象为),未转换为!(编程语言C中的逻辑否定语义)。
líng
/ lín
换成0
、零
;形象字无
、女
、母
、雌
、阴
;yī
换成1
;形象字有
、男
、公
、雄
、阳
、单
、独
; ai
/两
换成2
,亦可替换双
、对
san
/shan
换成3si
/shi
换成4wu
/wo
换成5
,可替换我
、舞
liu
/lu
/lü
换成6
qi
/qu
/chi
换成7,ba
/bai
换成8
,jiu
换成9
,未
、不
、无
等否定字,也可代惊
、叹
谁
、何
、问
、几
dòu
点
、的
冒
分
双
单
重
星
、乘
、日
、阳
、花
、雪
、菊
jiā
减
、横
、连
等
斜
反转
shù
、长
下
、南
、低
、底
美
、弓
、龙
(盘在柱子上的龙) 、蛇
、谐音钱
、金
在
以及任何可滚动或环状意境的字,比如滚
、环
、圈
、圆
;还可替代所有有辶
的字,如:逃进近遁边随
等。井
、网
,也可谐音jǐng
百
、白
和
、兼
、且
上
、北
、高
云
、水
、雨
、风
、浪
、丝
等有波纹意境的字以及叠字的第二字{
可替代西
,}
可替代东
]
可替代中
、右
,[
可替代左
)
可替代月
,(
可替代刀
小
、大
、于
、入
、向
空
阳
可以替换成*
或者1
),如果当前密码缺乏数字,那就替换成数字,如果当前密码缺乏特殊字符,那就替换成特殊字符,如果都缺乏或者都有,也替换成数字。煮豆燃豆萁,豆在釜中泣(Z,rd7,@f]7)
。京东的密码就扩展成:j{基础密码}d
=>jZ,r,7,@f]7d
,淘宝的密码就是:t{基础密码}b
=>tZ,r,7,@f]7b
,亚马逊的密码就是:y{基础密码}mx
=>yZ,r,7,@f]7mx
,QQ的密码就是:q{基础密码}q
=>qZ,r,7,@f]7q
,以此类推。狗娘养的
,然后,去掉一切特殊字符的规则,基础密码换成Zdrd7Dzfz7。如果有的网站还不允许超过8位,那就只能再骂一句娘希匹
,基础密码换成Zdrd7了,再加上头尾,就是7位了。咏柳
唐·贺知章
碧玉妆成一树高,万条垂下绿丝绦。Byzc1|^Wtc_6~d
不知细叶谁裁出,二月春风似剪刀。!Zxy?cc2)C~4j(
绝句
唐·杜甫
两个黄鹂鸣翠柳,一行白鹭上青天。2ghlmcl1hbl^qt
窗含西岭千秋雪,门泊东吴万里船。Ch{0qq*Mb}5wlc
清明
唐·杜牧
清明时节雨纷纷,路上行人欲断魂。Qm4j~f~6^xrydh
借问酒家何处有,牧童遥指杏花村。J?9+?cyMtyzx*c
赠花卿
唐·杜甫
锦城丝管日纷纷,半入江风半入云。Jc4g*f~B>j~b>~
此曲只应天上有,人间能得几回闻。C7zyt^yRjnd?hw
乌衣巷#wyx
唐·刘禹锡
朱雀桥边野草花,乌衣巷口夕阳斜。Zqqbyc*W1xkx1/
旧时王谢堂前燕,飞入寻常百姓家。94wxtqyF>xc%x+
又背古诗词,又记住复杂密码,不亦乐乎。
昨天接到同事高禹的报告,说eql从0.0.72升级到0.0.96版本后,原来可以用的代码,有一处不能用了,但是如果把参数名从table改为table1,那就没问题。凭直觉感觉这应该是一个很有意思的BUG,我就他那边把出BUG的SQL和代码都要过来了,先添加一个新的测试用例。然后我就直接RUN这个测试用例,发现果然不通过。很纳闷,开始单点调试(DEBUG)看看,但是结果竟然反转了,竟然能通过了。多次试验皆如此。有意思有意思,简直百思不得其解了。
随着对代码更进一步的跟踪与分析,我逐渐发现了原因了。为了简单的重现原因,我截了两个图。
第一张图是加了两个断点跑的图,结果是v1和v2相等的,都是同样的一串内容。如下:
第二张图是去掉了第一个断点,只保留第二个断点,结果是v1和v2完全是不一样的东西。如下:
到底是为什么呢?从两张图上的右下角的console输出差别可以看出来,在debug断点时,IDE为了展示变量的概要信息,其实是做了一些调用的。在第一张图停在第一个断点的时候,IDE其实在背后调用了Map.isEmpty和Map.size方法,然后才是被调试程序调用了Map.get方法。而在第二张图只有一个断点的时候,IDE在Map.get方法调用之前,并没有调用Map.isEmpty和Map.size方法。
问题是,这个Map不是普通的Map,是一个JAVA的动态代理,如下:
所以,这个代理实现上有缺陷,取map中属性为table的值时,又去找了名字为table的属性,结果HashMap中刚好有这个属性,于是取出来了,v1的值就成了HashMap$Node类型了(见下图),导致了问题的存在,所以改成table1就没这个问题,但是如果使用entrySet肯定有问题。
问题定位了,修复起来就简单得多,只需要在if判断Map类型之外,取属性/方法的时候加一个else就行了。
当初为什么要做一个动态代理呢,其实这个跟eql的参数传递实现有关系。一开始是并没有这个动态代理的,直到某一天,生产环境上老是报告空指针异常,然后异常的堆栈就指向了eql的内部实现。我一查,都是使用JavaBean传递参数遭遇问题,因为JavaBean里面有一些属性并没有被sql用到,却调用get方法,导致空指针异常。比如下面这样:
public class Person {
// ...
public String getName() {
return name;
}
public String getImage() {
throw new NullPointerException();
}
}
-- [findPersonByName]
SELECT NAME,SEX,EMAIL,ADDRESS FROM PERSON WHERE NAME = #name#
eql的使用者感觉很无奈,sql里面只用到了name啊,并没有使用image,为什么要调用image方法呢。确实是这样子,刚开始时,我是为了省事,把Bean的所有属性都转换成Map使用了,而不会管这个属性在sql中有没有实际使用到。(注:JavaBean的getter/setter方法应该只是简单的get和set,抛出异常是违反约定的)。所以为了避免这种无用的调用,我就开挂了Map的动态代理,只有等真正需要读取属性的时候,才去调用(Map就用get取,JavaBean用getter或者filed取)。然后这就种下了本次问题的因了。
Debug断点模式下,IDE为了展示堆栈上变量的信息,经常会调用一些方法,比如toString()方法,比如Map.isEmpty,Map.size等等。这些方法一般情况下都不会有问题,所以DEBUG与非DEBUG行为上一般都是一致的。但是在某些特殊情况下,却可能产生不一致的行为,比如本案所示,比如当初我在跟踪druid的sql解析代码时,就发现IDE的DEBUG窗口的一些变量直接就报告了空指针异常,然后我发现原来作者对变量的toString()方法实现没有特别考虑和测试。
吃完晚饭,准备看所谓"权游史上最好看的第一集"《权力的游戏》最新的S07E01,打开MAC,突然发现右手手背上残留了一粒米饭。我先想看看垃圾桶,感觉离得有点远,懒得动身去扔;然后再想留在桌上,一会肯定忘记,会再次粘到身上或者衣服上;干脆一口吃了。
吃完一想,以前背诵古诗《锄禾》,不就说的是要这么做的嘛。锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦。古诗背完,再一想,这才是知行合一。
然后再谷歌一把,一粒米粒中竟然包含有佛理,“佛观一粒米 大如须弥山”。
佛陀开示道:“无二之性,即是实性。一粒稻穗从最初的播种起,经过灌溉、施肥、收割、制造、贩卖……累积了种种的力量与辛苦才能成就一粒米,它所蕴含的功德是无量的,正如同那件裤子是贫苦夫妇唯一的财物、全部的家当,它所包藏的心量也是无限的!四海龙王懂得一粒米的功德与裤子的功德一样大,都由虔诚一念引出,所以赶紧退让称善。由此可见,只要虔诚一念,则小小一粒米、一条衫裤的力量,都可以与千千万万座须弥山相等!”
後来有人把这件事写成一首偈,来警示天下的冥顽众生:“佛观一粒米,大如须弥山;若人不了道,披毛带角还。”
吃完这一粒米饭,我竟然顺带参悟了一丁点佛道,南无阿弥佗佛!
最近连续接到东湖区静心瑜伽馆馆主的反应,说笑笑老师的私教课被连续约了,比如下午4点钟被人约了1小时私教课,5点钟又被人约了1个小时私教课,中间没有休息间隔。
图:笑笑老师私教课被连续预订了,中间没有休息间隔。
我们在系统参数设置上,有私教休息时间间隔,理论上这种情况不应该出现啊。然后我们查询了一下后台数据,发现确实如此:
图:笑笑老师私教课被连续预订了,中间没有休息间隔。
然后我们就想在开发环境、测试环境、生产环境体验馆上验证一下,总是重现不了,比如如下图,我先预定14:00开始的1小时私教课,那么接下来15:00开始的私教课就预约不了啊,只能约最近15:30开始的私教课,因为14:00开始的私教课在15:00结束时,需要休息半个小时,符合预期。可是生产环境为啥这样子呢,好郁闷。
图:14点1小时的课预订了以后,15点的时间点就选不到了。
既然暂时查不到问题,只能暂时搁置一下,继续做其他事情。但是客服接连报告了好几次,说馆主火气很大,要我们赶紧解决。然后每次又再看一遍,看看是否有可能有新的发现。直到昨天临下班的时候,客服又来说了:“馆主一天来找一趟,都不知道要怎么说了”,关键还带了一句:“已经有人订了周六16点的课了,现在展示的周六15点的还可以订”。豁然开朗,奶奶个X,原来是往前订没有时间间隔,之前测试的一直都是往后看。再回头来看,上面的图就已经体现了。问题只要能重现,修复就是很快的事情。
图:10点1小时的课预订了以后9点和11点的时间点就选不到了。
问题解决了以后,多了一个测试面试题,私教预约需要包含至少以下测试用例:
我们的瑜伽应用,部署在阿里云上。其中VPC中有一台ECS专门用来做公网出口(GW01),这样VPC中其它ECS就只分配内网IP就行了。GW01只用来做公网出口,以及系统管理入口,其它业务都不跑,就是为了让GW01保持简单可靠,防止这个系统中唯一的单点出现故障,导致整个系统对外部接口不可用。随着我们业务量的持续提升,我们对系统的可用性也越来越重视,我们就需要考虑,如何消除这个系统中唯一的单点。
翻阅阿里云的文档,有两种方案,一种是VIP方案,另外一种是NAT网关方案。
需要配合Keepalive进行配置。但是还是内测阶段,阿里云并没有上线此应用。
配置非常简单,申请NAT网关,绑定弹性公网IP,添加SNAT规则允许访问外部公网,添加DNAT规则允许外网访问VPC内网机器。
新鲜出炉的,我更新代码,随便看了一下更新列表,有这么一段代码:
@Sql("delete from f_user_role where user_id = ## and role_name = '候选人'")
void deleteRoleInfo(String userId);
然后,先不论“硬删除”与“软删除”,只论命名,我就习惯性的,在群里吼一声:
然后,再更新代码,看到了更新后的版本:
@Sql("delete from f_user_role where user_id = #1# and role_name = #2#")
void deleteRoleInfo(String userId, String roleName);
改的还是蛮及时的,看来吼一声达到效果了。
今天在维护群中,受到用户的一条反馈,说给会员开卡,明明开的12个月,咋就成了12个年头呢。
马上,查了一下数据,发现原来是馆主在卡激活之前做了一次卡编辑,误操作改成了12年。
在代码中顺手一查,代码中竟然有64处有年月天地方,看来这个单位没有很好地统一啊。
然后就顺手提取一个单独的类,来处理这些年月天周啥的,如下:
// 时间单位对象。
@RequiredArgsConstructor
public enum ExpiredUnit {
年("N", "年", ChronoUnit.YEARS), 月("Y", "个月", ChronoUnit.MONTHS),
天("T", "天", ChronoUnit.DAYS), 周("Z", "周", ChronoUnit.WEEKS);
@Getter private final String unit;
@Getter private final String suffix; // 单位作为数量后缀时的名称
@Getter private final ChronoUnit chronoUnit;
public static ExpiredUnit ofUnit(String unit) {
return Arrays.stream(ExpiredUnit.values()).filter(x -> x.unit.equals(unit)).findFirst()
.orElseThrow(() -> new RuntimeException("unknown expire unit " + unit));
}
public static boolean isValid(String unit) {
return Arrays.stream(ExpiredUnit.values()).anyMatch(x -> x.unit.equals(unit));
}
public static Expired of(String value, String unit) {
return of(NumberUtils.toInt(value), unit);
}
public static Expired of(int value, String unit) {
val expiredUnit = ofUnit(unit);
return expiredUnit == ExpiredUnit.天 && value % 7 == 0
? new Expired(value / 7, ExpiredUnit.周)
: new Expired(value, expiredUnit);
}
@Value
public static class Expired {
private final int value;
private final ExpiredUnit unit;
public String getDesc() {
return value < 0 ? "无限期" : (value + unit.getSuffix());
}
// 作为/后面的文字描述。例如1次/月,3次/2周
public String getOfDesc() {
return value == 1 ? unit.getUnit() : value + unit.getUnit();
}
public DateTime createExpired(DateTime effective) {
return effective.plusSeconds((int) (value * unit.getChronoUnit().getDuration().getSeconds()));
}
}
}
今天从代码基中看到有几行记录日志的代码,如下所示:
log.info("---cardPay-----{}", cardPay);
log.info("---isChargedSchedule ", isChargedSchedule);
log.info("--- cardPay.getTimes() > 0-----{}", cardPay.getTimes() > 0);
log.info("--- isNotBlank(cardPay.getPayId()) -----{}", isNotBlank(cardPay.getPayId()));
log.info("--- All -----{}", isChargedSchedule && cardPay.getTimes() > 0 && isNotBlank(cardPay.getPayId()));
if (isChargedSchedule && cardPay.getTimes() > 0 && isNotBlank(cardPay.getPayId())) {
我想,原始作者,就是为了从日志中追踪下面那个复杂的if语句是否命中。
但是这种写法有一些问题:
cardPay.getTime()
就可以了,何必记录cardPay.getTimes() > 0
;cardPay.getPayId()
就可以了,何必记录isNotBlank(cardPay.getPayId())
;cardPay.getTime()
和cardPay.getPayId()
了;---
干啥子(类似的还有打印一堆###
或者+++
之类的),只会白白地增加日志的体量,让日志文件增长的更快之外,根本无助于日志查找(干过平台的知道,偶尔为了在大几G的日志中找点东西,vi打开以下贼慢啊,而且还要定时清理大文件)。顺手做一下重构,代码如下:
val hasEnoughBalanceForSchedule = isChargedSchedule
&& cardPay.getTimes() > 0 && isNotBlank(cardPay.getPayId());
log.info("cardPay:{}, isChargedSchedule:{}, hasEnoughBalanceForSchedule:{}",
cardPay, isChargedSchedule, hasEnoughBalanceForSchedule);
if (hasEnoughBalanceForSchedule) {
这样子的好处如下:
最近看了一本科普书,叫做《时间的形状》,写得很上口,我几乎是几天之内就读完了。(相比之下,另外一本老外写的《物理世界奇遇记》,就很不对我的胃口,觉得就写得没劲,读了几段就扔一边去了)
书的序言就吸引了我,相对论之后,上帝改口了:“人类一思考,上帝就吓尿”了。
伽利略:“亲爱的亚里士多德先生,您不是说重的东西比轻的东西下落得更快吗?那么如果我们把一个铁块和一个木块用绳子拴在一起,从高处扔下来会发生什么?按照您的说法,较轻的木块下落得慢,因此它会拖累铁块的下落,所以它们会比单扔一个铁块下落得慢一点,是不是这样?”
亚里士多德:“没错,逻辑正确。”
伽利略:“但是,铁块和木块拴在一起以后,总重量却要比一个铁块更加重了啊,那么岂不是它们又应该比单个铁块下落得更快?”
亚里士多德:“呃……”
伽利略:“这个实验不用实际去做了吧,单单就在我们脑子里面做一下就可以发现您的理论是自相矛盾的。”
亚里士多德:“你让我想想,你让我想想……”
光速与光源的运动无关,对于任何参考系来说,光在真空中的传播速度恒为c。
还是思维实验:假设我现在一个人在黑漆漆的宇宙中飞行,虽然我飞得跟光一样快,但是因为没有任何参照物,我感觉不到自己的速度,就我自己的感觉而言和静止是一样的。这时候如果我身边有一束光,或者一个电磁波,我将看到什么呢?一束和我保持静止的光吗?一个静止的电磁波吗?也就是看到一个虽然在振荡的电磁场 ,但是它却不会交替感应下去吗?哦,不,这显然违背了麦克斯韦的方程组,波的速度和波源的运动速度无关,虽然我在以光速飞行,不论是我自己用发生装置发生一个电磁波,还是我飞过一个电磁波发生装置,我看到的电磁波都应该是相同的,因为介质没有变。我将看到一个振荡中的电场能够产生振荡的磁场,而一个振荡中的磁场又能够产生振荡的电场,这个交替反应绝不会停下来。再想象一下报数的情况,如果我和这队报数的人都在一节火车车厢中,火车高速行驶,但是我并不能感觉到火车是静止的还是运动着的,我会看到报数人的反应速度提高了吗?这也显然很荒谬,火车跑得再快也应该跟报数人的反应速度无关,我应该仍然看到它们以同样的反应速度传递着“一、二、三……”才对啊。
这么说来,光速应该相对于任何参照系来说,都是恒定不变的。哦,我这个想法实在有点疯狂。
他拿起笔在草稿纸上写下一句话:光速与光源的运动无关,对于任何参考系来说,光在真空中的传播速度恒为c。
在任何惯性系中,所有物理规律保持不变(相对性原理)。
同时性的相对性。
空间会收缩
2018年03月16日 19:41张半仙在QQ上跟我吐槽:"原来我也不高兴玩shiro 但是现在发现shiro 确实挺好用的,只是我们没优化好,并且复杂化了",并且表示这几天要好好研究,然后我说我也好好看看吧,并且写一篇博客,就叫《半仙说SHIRO其实很好用》,你也可以写写,咱们写的角度肯定不一样。
维基百科这么说:Apache Shiro(读作“sheeroh”,即日语“城”)是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。我随即谷歌一下关键字“日语 shiro”,真是以讹传讹,明明是白色的意思,不知道从哪里开始变成城
的意思了。白色白(しろ= shiro)ホワイト(White)黑色黒(くろ= kuro )ブラック(Black)红色赤(あか= aka )レッド(Red)
。
可见,历史还是很悠久的,都15岁了。
理解框架,首先的理解概念,Shiro的概念挺好理解,就是名字取得差劲了点。什么Subject, Realm,跟单词本身含义一点都挂不上边。
Subject表示当前的操作用户
,在安全领域,术语“Subject”可以是人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。这些解释跟Subject这个词感觉靠不上边,所以每次看完隔一段时间,就记不起来了Subject是个啥意思。所以,换个方式:Subject -> 主题 -> 猪蹄
,所以从卤猪蹄开始理解Shiro的概念。
Realm,单词本身是领域、范围的意思,在Shiro里面则是桥梁、连接的意思。Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当切实与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。记不住的话,就记得real+m(真的么?)你说的你是谁你要去哪里,都是真的么。
就是简单的"你是谁"(认证,是不是你)和“你要到哪里去”(授权,能不能去),两个词经常容易混淆。Authentication简称Authc,Authorization简称Authz,对了,必须先认证然后才是授权,所以按照字母顺序,应该是Authc然后是Authz,一下子记住了吧。
RBAC就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
今天,无意在朋友圈里又看了一遍朋友发的文章,致敬C语言之父—丹尼斯·里奇。复杂导致了Multics的失败,简单促成了Unix的诞生。文章中提到了两句话,我摘录一下:
吸取了Multics设计复杂而导致失败的教训,丹尼斯·里奇将Unix的设计原则定为”保持简约和直接”(Keep
it simple stupid),也就是后来著名的KISS原则。为了做到这一点,Unix由许多小程序组成,每个小程序只能完成一个功能,任何复杂的操作都必须分解成一些基本步骤,由这些小程序逐一完成,再组合起来得到最终结果。
C语言也贯彻了”保持简约”的原则,语法非常简洁,对使用者的限制很少。丹尼斯·里奇编写的教材《C编程语言》总共只有100多页,薄得难以置信。很多人都被它的简洁性吸引,学习并使用C语言。直到今天,C语言依然是世界上最重要的编程语言之一,”保持简约”原则显示了强大的生命力。`
我把里面的字眼“简单”替换成了“简约”,因为简约更加符合其本意。向里奇致敬,保持简约。要做到简约,却不简单。
做得欠缺(缺乏可维护性)的有:
shiro:cache:mobileCaptcha.authorizationCache:
,太长了;总结一下,Redis使用提升可维护性,需要考虑一下几点:
注:图中使用本人用GO语言开发的REDIS可视化工具go-redis-web
CREATE TABLE YOUR_TABLE (
-- ...
UPDATE_TIME TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
REMARK VARCHAR(100) NULL COMMENT '备注'
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='什么表';
ALTER TABLE YOUR_TABLE
MODIFY COLUMN UPDATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
ALTER TABLE your_table
MODIFY COLUMN CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
CREATE TABLE YOUR_TABLE (
-- ...
`CYCLE_VALUE` bigint(20) NOT NULL DEFAULT 0 COMMENT '限次周期',
)ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='什么表';
状态字段,使用中文词汇使用形容词,并且维护状态流转图,比如:
但是对于某些布尔含义的字段(比如可见性、是否匿名等),则不推荐使用中文形容词。直接使用tinyint,1表示true,0表示false。
表必须有主键,在无法使用业务字段作为主键的情况下,使用ID作为主键,ID的类型统一为bigint(20),并且在JAVA代码中使用WestId.next()生成。例如订单表的主键是订单ID;会员卡表的主键是会员卡ID;
表示范围Range的字段,采用左闭右开的原则。比如开始时间为2018年04月19日00:00:00,到期时间为2018年04月20日00:00:00,表示范围为[2018年04月19日00:00:00, 2018年04月20日00:00:00),判断有效期代码为'2018年04月19日00:00:00' <= now() and now() < '2018年04月20日00:00:00'。JAVA中也同样采取左闭右开的原则。
根据ID(作为主键)在表中进行查找时,不带其他条件。其它条件在代码中再次进行判断,这样可以本条SQL的重用率。比如:
-- 推荐写法
SELECT CARD_NO, USER_ID, STATE, AVAILABLE_BALANCE FROM T_MEMBER_CARD WHERE CARD_ID = '#cardId#';
-- 不推荐写法
SELECT CARD_NO, USER_ID, STATE, AVAILABLE_BALANCE FROM T_MEMBER_CARD WHERE CARD_ID = '#cardId#' AND STATE = '正常';
先欣赏短片。
这是一部仅仅6分钟的动画小短片,却让皮克斯动画耗时近三年才制作完成!除了画面细腻唯美、富有诗意外,我们还能在其中读出很多在成长教育中寓意深刻的道理。
晨曦微茫的小岛上,海水轻柔俯视着纤尘不染的沙滩。伴随着浪花的涌动,浮游生物、海藻、海螺、扇贝等海洋生命搁浅在陆地之上,等待它们的则是饥肠辘辘的捕食者。矶鹬(sandpiper)三五成群,鸣叫着落在了湿漉漉的沙滩上,啄食着可口的扇贝。许是经过了多年的历练,每当新一波潮水涌过来时,它们便第一时间轻盈地躲开,绝不会沾湿半根羽毛。不远处的灌木丛里,一只破壳未久的小矶鹬窥伺着父母的一举一动。少顷,妈妈飞回巢穴,小家伙一如既往张开嘴巴等着喂食,不过妈妈似乎决意要让孩子独立,自顾自将扇贝肉吃到肚子里。没有办法,小矶鹬只能走向海边。然而海水是那么讨厌,一波一波扑向这个未谙世事的小家伙。这时,一个意外突然发生……
然后,家长都容易得出一个观感:让孩子走出舒适区,是成长的第一步。问题是,自己也不是孩子,是不是也需要成长呢。答案肯定是,人需要经过不断的成长。那如何才能不断地成长呢,那当然是不断的走出当前的舒适区。随之而来的就是,当前的舒适区是什么?向哪个方向走出当前的舒适区?怎么做到不断的走出?这个就是本篇博客想一直探讨的内容。
TODO.
号称Poster是“跑死他”的邮递员,那么OData是个啥呢,难道是“殴打他”不成?下面这幅图,类比SQL还是很说明那么回事的。
OData - More Than Just REST
号称是Web时代的SQL(SQL for the Web),好处是,可以通过OData,粘合多种不同数据源,并且为不同的终端提供统一服务。
图:天空之境茶卡盐湖上的背影,来自500px
读了3天前的思特沃克一篇文章:写了这么多年代码,你真的了解SOLID吗?| 洞见,里面提到了SRP原则是最容易理解,但是却最难于驾驭的一个设计原则,有点像烹饪中“盐少许”一样,不太容易把握。
从SRP的S代表线条美来看,有人觉得胖点好,有人觉得瘦点妙,所谓环肥燕瘦,不能苛求。但是在一个全球化的时代,在一个信息满天飞的时代,这种审美潮流还是趋向一致的。
(未完待续)
要做到这一点,必须记住:
>>
的。下面的执行脚本演示了,使用nohup java -jar xx.jar 2>&1 > xx.log &
的情况下,清除日志,ls -lh
看到的大小不会变化,但是du -sh
能看到变化,二者不一致。
如果使用追加的方式,则没有问题。特此记录一下。
[app@app02 et-server]$ ls -lh et-server.log
-rw-rw-r-- 1 app app 1.2G 12月 22 17:36 et-server.log
[app@app02 et-server]$ du -sh et-server.log
1.2G et-server.log
[app@app02 et-server]$ > et-server.log
[app@app02 et-server]$
[app@app02 et-server]$ ls -lh et-server.log
-rw-rw-r-- 1 app app 1.2G 12月 22 17:37 et-server.log
[app@app02 et-server]$ du -sh et-server.log
8.0K et-server.log
[app@app02 et-server]$ ps -ef|grep et-server-0.0.1.jar|grep -v grep
app 22566 1 0 Dec08 ? 00:33:16 java -jar et-server-0.0.1.jar
[app@app02 et-server]$
[app@app02 et-server]$ kill -9 22566
[app@app02 et-server]$
[app@app02 et-server]$ nohup java -jar et-server-0.0.1.jar 2>&1 >> et-server.log &
[1] 27631
[app@app02 et-server]$ nohup: 忽略输入重定向错误到标准输出端
[app@app02 et-server]$ ps -ef|grep et-server-0.0.1.jar|grep -v grep
app 27631 27534 60 17:38 pts/0 00:00:03 java -jar et-server-0.0.1.jar
[app@app02 et-server]$
[app@app02 et-server]$ ls -lh et-server.log
-rw-rw-r-- 1 app app 1.2G 12月 22 17:38 et-server.log
[app@app02 et-server]$ du -sh et-server.log
164K et-server.log
[app@app02 et-server]$ > et-server.log
[app@app02 et-server]$
[app@app02 et-server]$ ls -lh et-server.log
-rw-rw-r-- 1 app app 0 12月 22 17:38 et-server.log
[app@app02 et-server]$ du -sh et-server.log
0 et-server.log
[app@app02 et-server]$
那天在上海迪士尼辛辛苦苦排队1小时看了很过瘾的5分钟《飞越地平线》后,就看到冯雨在QQ上向我抱怨,上周我调整过后权限系统貌似有问题,我很气愤,因为我比较讨厌貌似,有问题就有问题,为什么要貌似呢,然后就让我问测试去。
测试没有跟我提,我自己回到宾馆自己简单测了一下,没发现什么问题。周末在折腾完多租户连接池后,又整了一遍权限,也没有发现问题。就等下周一(今天)上班再说吧。
开完每日站会后,第一件事情,就是询问测试郑梦蓉,权限咋个不行。很快就报告了,说现在测试环境分店1上,馆主就看不了会员详情。我看了,果然如此。
我在代码中加了日志,重新部署了应用,然后再进入测试环境分店1上看会员详情,可以了。此时,再使用馆主登录到测试环境分店2上看会员详情,诡异的是,报告权限不足了。
然后我一通检查代码,检查缓存数据,好像没发现什么问题。然后只有打印更加详细的日志,在大量的权限日志中,发现了端倪,就是在mergeRoleRights
合并相同权限的服务包ID后,发现使用注解标注的权限,被丢失了。
/**
* 访客和会员合并后的 访客和会员的详情页
*
* @param userId
* @return
*/
@YogaFunctionRight(roles = {RoleIds.MERCHANT_100, RoleIds.SERVICE_102,
RoleIds.MANAGER_108, RoleIds.ADVISOR_107}, pkgs = {Pkgs.企业版_10011200, Pkgs.创业版_10011100})
@RequestMapping("/find-visitor-or-member-detail/{userId}")
public MemberDetailDvo findMemberDetail(@PathVariable String userId) {
return userService.findMemberDetail(userId);
}
然后我就再次检查mergeRoleRights的代码,终于发现了问题的真相了。
@Data @AllArgsConstructor @NoArgsConstructor
public class BasicRoleRight {
private String roleId;
private String rightCode;
private String servpackId;
private boolean merged;
}
private List<BasicRoleRight> mergeRoleRights(List<BasicRoleRight> allRoleRights) {
List<BasicRoleRight> mergedRoleRights = Lists.newArrayList();
for (int i = 0, ii = allRoleRights.size(); i < ii; ++i) {
val roleRight = allRoleRights.get(i);
if (roleRight.isMerged()) continue;
for (int j = i + 1; j < ii; ++j) {
val other = allRoleRights.get(j);
if (other.isMerged()) continue;
mergePkgIds(roleRight, other);
}
roleRight.setServpackId(roleRight.getServpackId() + ",");
roleRight.setMerged(true);
mergedRoleRights.add(roleRight);
}
return mergedRoleRights;
}
private void mergePkgIds(BasicRoleRight l, BasicRoleRight r) {
if (!isRoleIdRighCodeSame(l, r)) return;
val rPkgId = r.getServpackId();
if (rPkgId == null) return;
val lPkgId = l.getServpackId();
l.setServpackId(lPkgId == null ? rPkgId : lPkgId + "," + rPkgId);
}
问题在哪里呢,在BasicRoleRight对象的merged状态上,而本身BasicRoleRight对象是全局共享的,但是对于不同商户,会根据商户配置的服务包做一次过滤和合并,结果第一次访问的商户权限OK,后面访问的就不行了,因为merged状态已经被设置了。所以造成测试环境中,两个测试分店,重新发布后,第一个访问的分店权限可以,第二个就不行了。只要定位了问题,解决起来都是分分钟的了。干掉共享状态。
@Data @AllArgsConstructor @NoArgsConstructor
public class BasicRoleRight {
private String roleId;
private String rightCode;
private String servpackId;
}
private List<BasicRoleRight> mergeRoleRights(List<BasicRoleRight> allRoleRights) {
List<BasicRoleRight> mergedRoleRights = Lists.newArrayList();
Set<Integer> merged = Sets.newHashSet();
for (int i = 0, ii = allRoleRights.size(); i < ii; ++i) {
val roleRight = allRoleRights.get(i);
if (merged.contains(i)) continue;
for (int j = i + 1; j < ii; ++j) {
if (merged.contains(j)) continue;
val other = allRoleRights.get(j);
if (mergePkgIds(roleRight, other)) {
merged.add(j);
}
}
roleRight.setServpackId(roleRight.getServpackId() + ",");
merged.add(i);
mergedRoleRights.add(roleRight);
}
return mergedRoleRights;
}
private boolean mergePkgIds(BasicRoleRight l, BasicRoleRight r) {
if (!isRoleIdRighCodeSame(l, r)) return false;
val rPkgId = r.getServpackId();
if (rPkgId == null) return true;
val lPkgId = l.getServpackId();
l.setServpackId(lPkgId == null ? rPkgId : lPkgId + "," + rPkgId);
return true;
}
填平了这个坑后,真实地感受到了,要是使用Immutable对象的话,或许这种坑就不会挖了。
在知乎中看到一句话,深为欣赏,先记录下:
一致性设计是每一个体面的设计者都应当注意的问题。在神雕侠侣中,郭襄长大后与杨过的初次相逢是在风陵渡,到了倚天屠龙记中,灭绝师太告诉周芷若,她的师父、郭祖师的徒儿叫做风陵师太。权利的游戏中,詹姆把布兰从楼上推下,后来他儿子托曼也是坠楼而亡。前后呼应,因果轮回。更不用说红楼梦里让人津津乐道的草蛇灰线伏延千里。细细品味,回味无穷,所以它们才能成为经典。
In the real world, Rust is the coating closest to the bare metal.
图片来自于Why Programming Language “Rust” is Getting all the Love
没事翻翻slideshare上的ppt。一些觉得好的,收藏一下。
TL;DR: Rust is named after a fungus that is robust, distributed, and parallel. And, Graydon is a biology nerd.
I was fiddling with a number of names. Primarily it’s named after the pathogenic fungi which are just amazing multi-lifecycle, multi-host parasites, fascinating creatures.
But the name “rust” has other nice features. It fits with our theme of being “close to the metal” and “using old well-worn language technology”. It’s also a substring of “trust” and “robust”. What’s not to like?
Liigo的翻译
我从很多名称中挑选出了"Rust"。主要受到病原菌的启发——那些令人惊讶的、生死轮回的、多源寄生的奇妙物种。而且"Rust"还有其他内涵:它很好地契合了“贴近金属”(bare metal)、“复古编程语言技术”的主题;它从字面上融合了"Trust"(信任)和"Robust"(健壮)。 —— Graydon Hoare
图来自:https://www.slideshare.net/InfoQ/rust-systems-programming-for-everyone
这些常数,我会慢慢的背下来,以此证明,我还没有老。
图:铯原子钟
一秒被定义为铯-133(铯与钠为同一主族元素)原子基态的两个超精细能级之间跃迁对应辐射9,192,631,770个周期的时间。
1972年,美国的K.M.埃文森等人直接测量激光频率ν和真空中的波长λ,按公式 (其中v为真空中电磁波的速度, 为真空磁导率, 为真空介电常数)算得c=(299792458±1.2)米/秒。1975年第15届国际计量大会确认上述光速值作为国际推荐值使用。既然真空中的光速已成为定义值,以后就不需对光速进行任何测量了。
e,作为数学常数,是自然对数函数的底数。有时称它为欧拉数(Euler number),以瑞士数学家欧拉命名;也有个较鲜见的名字纳皮尔常数,以纪念苏格兰数学家约翰·纳皮尔 (John Napier)引进对数。它就像圆周率π和虚数单位i,e是数学中最重要的常数之一。
它的其中一个定义是 ,其数值约为(小数点后100位):“e ≈ 2.71828 18284 59045 23536 02874 71352 66249 77572 47093 69995 95749 66967 62772 40766 30353 54759 45713 82178 52516 64274”
圆周率(Pi)是圆的周长与直径的比值,一般用希腊字母π表示,是一个在数学及物理学中普遍存在的数学常数。π也等于圆形之面积与半径平方之比。是精确计算圆周长、圆面积、球体积等几何形状的关键值。 在分析学里,π可以严格地定义为满足sin x = 0的最小正实数x。
一起来背圆周率:
3.1415926535897932384626 433832795028841971693993
75105820974944592307816 40628620899862803482534
21170679821480865132823 06647093844609550582231
72535940812848111745028 41027019385211055596446
2294895493038196442881 09756659334461284756482
33786783165271201909145 64856692346034861045432
66482133936072602491412 73724587006606315588174
88152092096282925409171 53643678925903600113305
30548820466521384146951 94151160943305727036575
95919530921861173819326 11793105118548074462379
96274956735188575272489 12279381830119491298336
73362440656643086021394 94639522473719070217986
09437027705392171762931767523
最近对多租户数据库连接池做了一下改造。改造之前,是一个商户一个小池子,数据库连接只是在小池子里共享。改造之后,是所有商户都一个大池子,数据库连接在大池子**享。每个商户请求时,从大池子里拿连接,然后切换到自己的商户库中。
效果还是杠杠的,一下子把以前的尖刺给削平了。纪念一下。
从连接池中获取连接后,需要将连接切换到当前请求的商户库上。但是如果连接池上次使用时,就是当前商户库,则跳过切换的动作。这个设计本意是利用状态,避免重复切换。但是在实际部署过程中,由于新开商户的数据库,暂时需要手工配置账户权限,导致没有配置权限之前,出现串库现象。很快就从代码中,找到了问题。
今天给小组开发人员做了一次简单的分享,分享完了,再给取了这么一个名字,因为联想到了金龙鱼食用油曾经到处轰炸的广告词1:1:1(随便百度一下,才知道是指 不饱和脂肪酸:中度不饱和脂肪酸:饱和脂肪酸 为1:1:1)。我这里指,业务上的一项配置,对应到配置表中的一条记录,对应到代码中的一个Class,所以也是1:1:1。
我把这种1:1:1也称之为一一映射,这个是数学中的概念,但是应用到业务开发中来,也是一种很好的借鉴。这种映射,是一种天然的映射,非常简单明了,无需经过大脑二次转换,是非常直接的方式。话说如果原始人最多只能数到数字3,那么Ta怎么能确定左手和右手的手指头数是一样多呢?对了,两只手掌对起来,形成一一映射,左手有一只大拇指,右手有且仅有一根大拇指与其映射,反之如此。
更多的关于构建一一映射,可以翻阅《图灵的秘密》这本书。比如,里面说到能与自然数数列形成一一对应关系的集合,称之为可数的;比如0与1之间实数,可以与大于1的实数一一对应,只需要每个数求一下倒数即可。
PS:
原始设计:
存在问题:1) 一条业务需要对应到多条记录,是在(业务类型、发送类型、发送角色)三者之殇一个笛卡尔全集;2) 根据触发条件(TRIGGER_SITUATION)进行查询配置,缺乏与业务的直接映射;3) 页面上需要写死诸多业务代码;总之,不是一一映射,是一对多映射,缺乏直观性,不利于数据维护。
那么多的属性,怎么会不遗漏呢?答:还是一一对应。使用IDEA的分栏模式,Alt+鼠标选择的列模式,再加数字顺序编号(String Manipulation插件)。
在枚举变量上,我采用了一以贯之的“大白话”的形式,不做任何编码约定,直接使用“馆主”,“店长”,“教练”,“会籍”,“客服”的字眼,而不是之前的编码100、108、101、107、102。这种编码我总是记不住,每次我都得去下面这张表去找答案(也或许是我主观不愿意去记忆这种编码的原因了)。
图片🦅抓🐰,来自500px
控制论,看起来很无聊,然而事实是,这本书一!点!也!不!无!聊!《控制论与科学方法论》是一本非常有趣的科普作品!作者用了大量生动的事例,深入浅出地解释了“控制论”这个看似高深的概念。
系统论要求把事物当作一个整体或系统来研究,并用数学模型去描述和确定系统的结构和行为。所谓系统,即由相互作用和相互依赖的若干组成部分结合成的、具有特定功能的有机整体;而系统本身又是它所从属的一个更大系统的组成部分。贝塔朗菲旗帜鲜明地提出了系统观点、动态观点和等级观点。指出复杂事物功能远大于某组成因果链中各环节的简单总和,认为一切生命都处于积极运动状态,有机体作为一个系统能够保持动态稳定是系统向环境充分开放,获得物质、信息、能量交换的结果。系统论强调整体与局部、局部与局部、系统本身与外部环境之间互为依存、相互影响和制约的关系,具有目的性、动态性、有序性三大基本特征。
它是用概率论和数理统计方法,从量的方面来研究系统的信息如何获取、加工、处理、传输和控制的一门科学。信息就是指消息中所包含的新内容与新知识,是用来减少和消除人们对于事物认识的不确定性。信息是一切系统保持一定结构、实现其功能的基础。狭义信息论是研究在通讯系统中普遍存在着的信息传递的共同规律、以及如何提高各信息传输系统的有效性和可靠性的一门通讯理论。广义信息论被理解为使运用狭义信息论的观点来研究一切问题的理论。信息论认为,系统正是通过获取、传递、加工与处理信息而实现其有目的的运动的。信息论能够揭示人类认识活动产生飞跃的实质,有助于探索与研究人们的思维规律和推动与进化人们的思维活动。
同他的合作者自觉地适应近代科学技术中不同门类相互渗透与相互融合的发展趋势而创始的。它摆脱了牛顿经典力学和拉普拉斯机械决定论的束缚,使用新的统计理论研究系统运动状态、行为方式和变化趋势的各种可能性。控制论是研究系统的状态、功能、行为方式及变动趋势,控制系统的稳定,揭示不同系统的共同的控制规律,使系统按预定目标运行的技术科学。
根据创始人维纳(Nobert Wiener)的定义,控制论(Cybernetics)是“关于动物和机器中控制和通信的科学”,简言之,控制论的中心问题就是控制与通信。维纳的定义中同时声明了控制论的研究对象,即动物和机器。然而广义上的机器这个概念涉及的范围极为广泛,所以控制论这门学科极具普适性。
所谓同构,就是两个系统之间存在某种“一一对应”的关系,所有的状态只要经过一个统一的变换就能把这一个系统变成另一个,不外如是。照片与底片是同构的,摩天大楼与它的设计图纸是同构的,纸质书与它的电子版也是同构的。
同构的要求太过严格,所以很多情况下难以实现,比同构更广泛也更深刻的现象是同态。同态的意义也很简单,如果说同构是相等,那么同态就是相似。同构要求两个系统之间建立“一一对应”的联系,同态只要求两个系统之间建立某种“多一对应”,即要求两个系统某些部分相等,或者忽略某些性质后同构。假如我是一个只能看到红色的红绿色盲,那么我看到的红绿灯与正常人看到的红绿灯是同态,正常人看到的红绿灯经过(红,绿)->红的变换后就与我看到的同构了。
由此可见,建立同态的一个重要手段就是模糊化,忽略一些部分的同时保留重要的性质,将多个状态或者多个部分看成一个,就可建立一个更简单的同态系统。说到这里,那两个字就要呼之欲出了:建模。模型大多是对现实世界的简化,通常情况下是理想的、不会在现实世界中出现的系统,然而却能帮助人们考察研究对象的本质。
控制论的一个重要意义就在于,它能够帮助我们建立这样的同构/同态,方法就是构造带状态的、抽象的能动系统。同构/同态都保留了原象的性质(构造同态的时候一定要将想要研究的性质保留下来),要利用这一点,通过建立与被研究系统同构/同态且易于研究的系统,去研究“那个系统”的性质。这便是同构/同态的意义。
早就听人推荐过金观涛、华国凡两位老师写的《控制论与科学方法论》,但忌惮于「控制论[^cybernetics]」这三个字,直到最近才找来读读。我去~~这本小书早该绝版了,让这么多人窥见上帝的秘密!两位老师写作手法之动人,说这是一本小说也不为过。实在不能想象这是在 1983 年写的😱
发射嫦娥三号到月球,火箭为什么不像打枪一样直接往月球奔?想想看,地月380000km的距离,卫星才多大,这无异于用狙击枪打1000m外的蚊子,你再精准也打不到,况且蚊子在动,月球也在动。
再来看一个例子,老鹰抓兔子,兔子一慌就赶紧跑,老鹰不可能在高空看见兔子跑的那一刻就算好兔子的运动方程吧。数学不好没关系,咱有眼睛估计一下目标兔子的大致距离,先大致地冲过去,然后一直盯着它,不断向鹰脑报告和目标的差距,连续地调整动作以减小目标差,直到目标差为0,逮到兔子。这只老鹰之所以成功,是因为它用了负反馈调节的方法。
负反馈调节:通过系统不断把自己的控制后果与目标作比较,使得目标差在一次一次控制中慢慢减少,最终扩大了控制能力的过程。
老鹰抓兔子用的这套负反馈调节的办法用在了导弹打飞机上。导弹上装了红外线装置(眼睛),配上计算机(大脑),不断把位置(控制后果)和飞机(目标)作比较,减少目标差到0,把飞机炸开花。当然发射月球卫星也采用了这个办法。
负反馈调节提供了解决许多实际问题的思路。
做一款APP,有的人忙了半天,憋出了一些个Big Idea,说我这个创意用户肯定喜欢,然后就开始做,还一定要花很多时间开发出许许多多功能,自以为把东西做得非常完善了,才让这个产品上线,接着在很多个渠道做推广,说我这个产品怎么怎么好。这种做法的错误在于自己意淫用户的需求,看起来一直在做,实际上根本没有把控制后果(APP内容)和目标(用户需求)作比较,不懂反馈,十有八九会失败。
正确的做法则是运用负反馈调节的理念,先有一个想法:用户可能对这个需求感兴趣。再去做用户调查,明确我想要开发的功能和用户真正的痛点之间到底有多少差距,这就是目标差。先做出一些基本的功能,在两个渠道做推广,接着回收数据并一直进行着用户调查,通过多次反馈来实现产品的迭代优化。
这里用到的“用户调查——内容制作——投放渠道——数据反馈——调整优化”的理念也是优秀的产品经理和运营人员必备的思维。
而看了10个减肥技巧、学了100个自我管理的方法、收藏了1000套书单,不行动,它们永远是信息的垃圾。
就像我写的这篇5000字的文章,获得知识——思考整理——写成文字,这个过程让我自己对控制论的理解要比没写文章之前要更加透彻。
控制论与科学方法论.pdf
一本小书,很容易就随手翻完的,然后再针对里面的一些**,细细琢磨。
<Linux/Unix设计**>一书主要介绍了unix系统设计中的一些原则,其中包含了九个主原则和十个原则。
摘
即“小即是美”。小则灵活,并易于改变去适应变化。而大则牵一发而动全身,一旦改变就会伤筋动骨。说到这个原则,就想起我们it界那个长久以来口口相传的笑话,说工程师老是被pd说改需求改需求,从而狂燥甚至发狂。笑一笑后,是不是该想一想,之所以出现这么难以改变的情况,是不是将系统设计和实现得太过大了呢?所以才会在有变化的时候如果痛苦?
即“让程序只做一件事”。这类似于OOP编程**中的SRP原则(单一职责),以前理解这个原则大约是觉得这样做的一个理由这样易于维护,或者可读性强上。但是看了这本书的介绍后发现,单一职责的优点不仅在可读性和维护性这些上面,还在性能上。因为一个方法如果小,则在调用完毕后,它使用的局部变量等就可以马上回收,降低资源的开销。
即“尽快建立原型”。这里提到的一个产品的三个系统感觉相当有意思。所谓三个系统分别是:第一系统,是指在资源紧迫情况下,而开发的预研性质的系统;第二系统是在第一个系统出现后,看到第一个系统的价值后,也看到第一个系统实践的结果后而产生的,在第一系统的基础上做出大量的优化和扩展;第三系统是在第一和第二系统出现后,去芜存精,最终比较完美的一个产出。如果投身于第一个系统的开发,你要冒很大的风险,因为它极有可能失败,但是在开发第一个系统的团队中工作将是一件最有激情的事.如果投向第二个系统,前期还好,你有可能接触到最顶尖的设计,但是到后期,就没意思了,就是重复的添加功能而已。不过不经历第一系统的后期也无法进入第三系统,所以在工作中,评估产品的阶段,选择投向其中,未尝不是一个好的方法。
即“可移植性高于效率”。之所以推崇这个是因为作者认为计算机的性能总是越来越强,因此效率应该不是程序关心的第一优先级,但是向更高性能计算机的移植性却是程序应该关注的。这个我觉得倒是有所保留,近年来随着处理数据的海量化、什么云啊,大数据啊,就说明现在的计算机的性能增长速度还赶不上我们要处理的数量量增长速度。而且最近几年并发编程为什么这么火,因为硬件生产商已经无法保持业界的摩尔增长率了,所以只能通过软件的方式来保持性能的增长。而并发多核的编程就是在这种情况被程序员所追捧。
即“使用纯文本来存储数据”。为什么呢?因为这样可以使数据具有最大的可移植性。
即“充分利用软件的杠杆效应”。在编程中有一个原则尽量去重用别人已有的代码,来提高自己的工作效率。但是有时候你不得不承认,去读懂一些人写得代码,远没有自己重写来得简单。因此如果你要重用别人的代码,那么尽量重用一些优质的代码吧,并且要时刻提醒自己,别人的代码是不安全的。。。
即“尽量使用shell脚本”。普适地来讲,就是我们在实现程序时尽量选择一些可移植性高的编程语言,比如说java。
即“避免强制性用户界面”(Captive User Interface)。这个不是太好理解,也没遇到过这种场景,以后再说。。。
即“将程序用途过滤器”。就是使程序拥有输入、输出和异常处理,这样就可以将一个大型程序分解成若干个过程,并相互约定沟通方式,这不就是我们多人并发完成某项功能所需要做的事情吗?
即“让用户定制系统”。这个对于选择linux的人来说很有感受,什么东西都想控制在自己手中。但是说到底还是要了解你的产品所面向的群体对这个产品的定位,有些人就是想傻瓜一点,那就傻瓜一点吧。
即“使内核最小化”。这也符合了小即是美的原则,如果将系统内核设计得小而轻量,然后其他人可以自由的在此基础上扩展,这就是一个完美的方案了。成为其他人的平台,进而构建一个生态圈,是由小变大,立于不败的不二法则。
即“统一小写”。这个属于个人风格了。
即“不要将数据固化到纸上,节约树林”。
即“沉默是金”。指有些场景不需要向用户反馈信息时,就不要反馈。
即“并行思考”。多核计算机总要发挥它的作用吧。
即“部分之和大于整体”。这个取决于一个好的设计,如果将各部分组合成整体的成本太大,则有可能部分之和小于整体了。
即“寻找90%的解决方案”。这个大家估计深要体会,有时候为了解决那10%甚至1%才会出现的问题,往往要投入巨大的精力,这个时候舍弃那1%的杞人忧天也许未尝不是一个好的抉择。不过如果你要冲向巅顶的话,你势必要面临这1%的挑战,这也是程序员乐此不疲的事情。
即“更坏就是更好”。不过理解起来似乎是与其做不好,还不如选择一个已有较差的方案。
即 “层次化思考”。对系统、软件分层,有利于维护开发。
今天改造了一下瑜伽系统的权限设计,可以使用注解的形式,直接在代码上面声明权限了,如果不声明,则访问会被显式拒绝,比如:
/**
* 查询员工优惠权限.
*
* @return 员工优惠权限.
*/
@YogaFunctionRight(roles={RoleIds.MERCHANT_100, RoleIds.MANAGER_108}, pkgs = {Pkgs.企业版_10011200, Pkgs.创业版_10011100})
@RequestMapping(value = "/query-staff-preferential-right", method = GET)
public StaffPreferentialVo queryStaffPreferentialRight() {
return mbrCardService.queryPreferentialRight();
}
之前则需要给平台工程师张存鑫发送邮件,在表中添加权限,比如:
insert into tt_d_funcright(RIGHT_CODE, RIGHT_NAME, RIGHT_TYPE, RIGHT_DESC, SERVPACK_ID, `URL`, CHAIN_NAME, CHAIN_DEFINITION, `ORDER`) values
('CARD-0036', '员工开续卡权限设置', '2', '员工开续卡权限设置', '10011200', null, '/mbr-card/update-staff-preferential-right', 'perms[CARD-0036]', '1'),
('CARD-0036', '员工开续卡权限设置', '2', '员工开续卡权限设置', '10011100', '', '/mbr-card/update-staff-preferential-right', 'perms[CARD-0036]', '1'),
('CARD-0037', '员工开续卡权限查询', '2', '员工开续卡权限查询', '10011200', null, '/mbr-card/query-staff-preferential-right', 'perms[CARD-0037]', '1'),
('CARD-0037', '员工开续卡权限查询', '2', '员工开续卡权限查询', '10011100', '', '/mbr-card/query-staff-preferential-right', 'perms[CARD-0037]', '1');
insert into tt_f_role_right(ROLE_ID, RIGHT_CODE, BEGIN_TIME, END_TIME, OPERATOR_ID, GRANT_TIME) values
('100', 'CARD-0036', '2017-07-14 16:27:55', '2117-02-24 16:27:55', '0', '2017-02-24 16:27:55'),
('108', 'CARD-0036', '2017-07-14 16:27:55', '2117-02-24 16:27:55', '0', '2017-02-24 16:27:55'),
('100', 'CARD-0037', '2017-07-14 16:27:55', '2117-02-24 16:27:55', '0', '2017-02-24 16:27:55'),
('108', 'CARD-0037', '2017-07-14 16:27:55', '2117-02-24 16:27:55', '0', '2017-02-24 16:27:55');
然后等到在测试环境部署时,在测试环境上执行一下;等到再生产环境部署时,在生产环境执行一下。
这里面有几个问题,容易出错:
这两个问题不止出现过一次,而是多次发生。顾聪和冯雨都说,某个没有配置的URL我发了权限变更邮件啊,还翻出邮件记录。我相信他们发了,也相信张存鑫在版本发布的时候,各种问题忙起来很容易就漏掉了。我跟冯雨说,换你来搞平台,也很有可能在忙的时候漏掉变更邮件的执行。所以最好从技术上把这个人的容易出错的因素降到最低。
最大的问题的是,改造前的系统,如果没有在tt_d_funcright和tt_f_role_right中配置,则默认为登录即可访问。造成的结果是,本来应该只能给馆主的权限,结果漏掉配置,实际上所有登录用户都可以访问了。这个问题在改造前的设计里面被隐藏起来了(水面之下),所以没人会注意到。而在新的设计中,只要漏掉了,那么就直接无法访问,让问题立即浮出水面了,即使忘记了,也会有立即反馈。
所以,好的设计,不是去掩盖问题,而是让问题立即浮出水面,从而形成一种良好的“负反馈”机制。
想到之前我一直讲的挑水的例子,大水缸离水源有20米,有10个人一起领到一个小任务,就是把大水缸的水满上。有两种方式来完成这个任务:第一种是10个人各自拧着水桶去水源地取水,然后各自拧到水缸去倒水,直到水满;第二种是10个人在水缸和水源之间排成一条长队,然后接力传输满水桶到水缸,再接力传回空的水桶。两种方式各有优劣,但是从“让问题浮出水面”的设计思路来看,显然第二种方式更好。第二种方式显得更加有秩序,一旦有人生病了、摔跤或者偷懒了,那么马上水就无法传递,接力就停止了,问题马上显现出来了。第一种方式显得就混乱,有人偷懒有人摔跤有人生病就很难看出来。
回到权限系统的设计改造上来,改造完了以后,除了能让权限问题立即浮出水面之外,而且更加省事了(再也不用发邮件,不用平台人员执行邮件了),不亦乐乎。
股神巴菲特(Warren Buffett)的话说:"只有退潮了,才知道谁在裸泳"(You only find out who is swimming naked when the tide goes out.)。放在技术上,如果问题被掩盖了,那么等黑客攻击安全问题爆发的时候(退潮),那就惨了(裸泳暴露)了。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.