现在生活中越来越多的“低头族”,科技的发展既拉近了人与人之间的距离,也推远了人与人之间的距离。
即使是家庭聚会,大家也更沉迷于手中的那三寸的世界。是时候放下手机,多感受身边的美好。
“Phubbing”这个词即是由Macquarie词典创造来形容这一因使用手机而冷落身边人的现象。它出现于2012年5月,是在McCann邀请词典编纂者、作家和诗人创造一个词描述该行为的背景下产生的。那时开始,“拒当低头族”这个活动也就由McCann推广开了。1
大家可能没听过McCann,它就是制作了“蠢蠢的死法”youku/Youtube的那家广告公司。
“拒当低头族”活动的网站2以及相关的Facebook页面,虽然是旨在推广澳大利亚Macquarie词典公关活动的一部分,但是这种提倡的生活方式确实值得我们肯定。
一部名为“Phubbing: A Word is Born”的短片,描述了整个过程。虽然是作为Macquarie词典的广告,但是“低头族”的这一现象确实是全世界都存在的。我们要想想手中的那三寸世界让我们错过了什么。
一时技痒,做了一张海报提醒大家放下手机,因为身边有更多美好和重要的事值得你去注意。
最近在用一个APP:Forest
在提高自己注意力的同时,赚取的金币积攒下来还可以让APP在现实中种一棵树,顿时感觉自己萌萌哒为更美好的世界贡献出了自己的力量。(严肃脸)
What → Why → How
的学习顺序来得到答案。
CNNIC是中国互联网络信息中心2(China Internet Network Information Center)的缩写,是经中华人民共和国国务院主管部门批准,于1997年6月3日成立的互联网管理和服务机构。中国互联网络信息中心成立伊始,由中国科学院主管;2014年末,改由中央网络安全和信息化领导小组办公室、国家互联网信息办公室主管。
在很多场合,中国互联网信息中心都代表中华人民共和国政府行使相应的职权。
担当“国家级的互联网络信息中心”之职
作为中国的顶级域名.cn
和中文域名注册管理机构
运行、管理域名系统,维护域名数据库
选择域名注册服务机构
监督、管理域名注册服务机构的域名注册服务
代表中国大陆各互联网络单位,与世界上其他互联网络信息中心,作业务联系
发布《中国互联网络发展状况统计报告》
规范域名注册服务
它的运行和管理工作,由中国科学院计算机网络信息中心承担
它在业务上受工业和信息化部领导
它在行政上受中央网络安全和信息化领导小组办公室、国家互联网信息办公室领导
CA是数字证书认证机构(Certificate Authority)的缩写。它是负责发放和管理数字证书的权威机构,并作为电子商务交易中受信任的第三方,承担公钥体系中公钥的合法性检验的责任。
数字证书为实现双方安全通信提供电子认证。它的原理是当在认定一个人身份时,是通过信任的另外一个人来进行的,即CA。
证书之间也存在着信任关系,是可以嵌套的。只要信任链上的第一个证书,那后续的证书都可以被信任。
证书之间构成一个树形关系,证书都要依靠上一级的证书来证明自己,而处于最顶上树根位置的根证书(Root Certificate)是不需要被证明。一旦根证书出了问题,后果是相当严重的。
HTTPS是超文本传输安全协议(Hypertext Transfer Protocol Secure)的缩写,是超文本传输协议和SSL/TLS的组合。它的主要思想是在不安全的网络上创建一安全信道,并在使用适当的加密套件和服务器证书被验证且被信任的情况下,对窃听和中间人攻击提供合理的防护。
HTTPS的信任是建立在数字证书认证机构发放的数字证书上的。
在密码学和计算机安全领域中,中间人攻击(Man-in-the-middle attack,通常缩写为MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。
正常情况下,访问一个网站是这样的:
一个安全的访问,没有受到监听。
然而愿望是美好的,现实是残酷的。如果伪造的证书来自CA,那么在一个网站被DNS劫持的同时,伪造的网站又提供了一个来自CA的证书,这时浏览器依旧认为这次访问是安全的,而用户就会被监听:
CNNIC证书就是这样的一个证书,当你和一个网站建立安全连接时,如果使用的证书是CNNIC证书,浏览器会默认这个连接是安全的。
CNNIC的做事风格4:
CNNIC制作的上网辅助软件“中文上网官方版软件”,由于其强制安装和无法彻底卸载,一度被北京市网络行业协会列入10款流氓软件名单并勒令整改5。为此,CNNIC起诉把它列为流氓软件的殺毒软件制作者6。
2009年12月9日,CNNIC被中国中央电视台曝光对.cn
域名管理不善,致使色情网站可以轻而易举的更换域名7。12月11日晚,CNNIC发出公告8,公告暗指从2009年12月14日9时起,禁止个人注册.cn
域名9。2010年1月底,CNNIC要求所有.cn
域名提交个人身份证复印件,由于没有出台适当的隐私保护条例,遭.cn
域名持有者抵制,不得已将个人身份证信息提交的最后的期限推迟到3月15日。但是提交信息的并不多。
最近,Google发现CNNIC颁发了多个针对Google域名的用于中间人攻击的证书10。这个名为MCS集团的中级证书颁发机构发行了多个Google域名的假证书,而MCS集团的中级证书则来自中国的CNNIC。该证书冒充成受信任的Google的域名,被用于部署到网络防火墙中,用于劫持所有处于该防火墙后的HTTPS网络通信,而绕过浏览器警告。
根据最新的消息,Google和Mozilla已经决定在Chrome和Firefox中禁用CNNIC的证书11。
如维基百科上所说:
这件事情再次显示,互联网证书颁发机制公开透明的必要性。
如何吊销CNNIC证书12:
打开Keychain Access
点击左栏的System Roots
找到China Internet Network Information Center
和CNNIC ROOT
双击
点击Trust
,将属性全部改为Never Trust
Win+R
运行certlm.msc
在左栏的Trusted Root Certification Authorities
中的Certificates
,找到CNNIC ROOT
Ctrl+C
复制
找到左栏的Untrusted Certificates
Ctrl+V
粘贴
Firefox的的证书体系是独立于操作系统的,所以在吊销了系统的证书后,还需要吊销它的证书:
⌘+,
打开Preferences
切换到Advanced
,点击Certificates
,点击View Certificates
切换到Authorities
里面的证书列表是按字母排序的,将China Internet Network Information Center
和CNNIC
之类的Edit Trust
全部清空。
这个是最方便的方法了,Github上的RevokeChinaCerts项目,可以按照步骤手动吊销。
打开https://www1.cnnic.cn/,请注意该链接是HTTPS,如果显示不安全,就表明操作成功:
去年第一次知道关于CNNIC证书零零散散的事迹,当时就听到“如果利用证书建立虚假网站构建大局域网该怎么办”的担忧,于是迅速的ban掉了CNNIC证书。
知乎现在删除了大量有关CNNIC的讨论,曾经有一个问题“CNNIC 证书值得信任吗?”,下面有一条回答是这样的:
脸打的好疼。
Octopress是Brandon Mathis在Jekyll上开发的,利用Github Pages来展示静态页面。
正如Octopress网站所说的
Octopress is a blogging framework for hackers. You should be comfortable running shell commands and familiar with the basics of Git.
所以如果你不喜欢这种方式,可以选择其他博客平台或框架,毕竟工具就是为了让我们效率最大化而不是造成困惑。
本文是基于OS X系统进行介绍的。
安装Git。
安装Ruby 1.9.3。
在Mac上使用brew安装Git:
1
|
|
同样的,使用brew安装rbenv之后,使用rbenv安装所需要的Ruby版本:
1 2 3 4 |
|
安装好后可以进行验证:
1 2 |
|
首先使用Git将Octopress从Github上clone到本地:
1 2 |
|
紧接着,安装依赖:
1 2 3 |
|
安装默认的Octopress主题:
1
|
|
有很多第三方的Octopress主题可供选择——3rd Party Octopress Themes。
通过git submodule add
将需要的主题项目加为子模块,接着安装主题:
1 2 3 |
|
我的博客使用的是whiterspace
主题。
正如Octopress的作者所说的,大部分情况下只需要修改Rakefile
和_config.yml
就可以了。其中Rakefile
是和部署有关的,除非你使用rsync,否则不需要动它。
在_config.yml
中,需要对三个部分进行配置。
1 2 3 4 5 6 7 8 9 10 11 |
|
这是和Jekyll和Plugins有关的,详情见Configuring Octopress。
这些第三方组件是包含在Octopress中的,只需要配置好就可以添加到自己的博客中。
Github:在sidebar中列出你的Github Repo。
Twitter:添加一个分享到Twitter的按钮。
Google Plus One:设置分享到Google +1。
Pinboard:在sidebar中分享你最近的Pinboard书签。
Delicious:在sidebar中分享你最近的Delicious书签。
Disqus Comments:访问Disqus,创建好账号登录后点击Settings
,点击Admin
,就可以为个人博客站点创建一个short name,把它添加到_config.yml
中的disqus_short_name
后面就可以在个人博客中使用Disqus进行评论。
Google Analytics:在Google Analytics中获取跟踪ID,把它添加到_config.yml
中的google_analytics_tracking_id
后面就可以使用Google Analytics对个人博客进行分析统计。
Facebook:添加Facebooklike
按钮。
Github的Pages service允许我们为自己的Repo创建展示页面。我们使用http://USER_NAME.github.io
作为博客的地址,当然你也可以使用自己的域名(怎么做)。
首先,我们在Github上新建一个Repo,把它命名为USER_NAME.github.io
,其中USER_NAME
是你在Github上的用户名。这是为了把master
branch作为web server,使用http://USER_NAME.github.io
链接展示你的页面。也就是说你需要在source
branch上进行工作,并把生成的内容push到master
branch上。
如果你觉的这些好麻烦,没事,Octopress会用一个配置task来帮助你把它们做好:
1
|
|
这个rake task会问你要Github Repo的URL。把上面我们新建的Repo的SSH或者HTTPS URL复制到这里(e.g. git@github.com:USER_NAME/USER_NAME.github.io.git)。
接着,它会为你做以下这些:
存储你的Github Pages仓库URL。
把指向imathis/octopress的remote由origin
重命名为octopress
。
把你的Github Pages仓库作为默认的origin
remote。
把active
branch从master
切换到source
。
使你的博客URL与你的仓库一致。
在_deploy
文件夹中设置一个master
branch用来部署。
紧接着运行:
1 2 |
|
rake generate
会把source
文件夹下面的markdown文件编译为html文件,并复制到public
文件夹下,因此public
下的结构跟source
的一致,里边的内容为最终的静态页面。
rake deploy
会将生成的静态页面复制到_deploy
文件夹下并把它们添加到git,commit然后push到Github Pages仓库的master
branch上。
最后用浏览器打开http://USER_NAME.github.io
,你就会看见自己的博客了。首次push可能会花费一段时间等待Github为你生成页面。
当然不要忘记把更改的文件push到source
branch上:
1 2 3 |
|
Github的Project Pages服务允许你为你的已存在的Repo提供一个站点。它会在你的Repo中寻找gh-pages
branch,把上面的内容在这个链接中展示http://USER_NAME.github.io/REPO_NAME
。
和上面步骤一致,只不过在运行rake setup_github_pages
后输入的是已存在Repo的URL。
这个的好处是可以把http://USER_NAME.github.io
留下来以后再使用,比如个人主页什么的。
博客存储在source/_posts
文件夹中,并按照Jekyll的惯例进行命名: YYYY-MM-DD-post-title.markdown
。文件的名字会被用作URL的一部分,日期会用来对文件进行区分和排序。
Octopress提供一个rake task来生成一篇新的博客:
1
|
|
创建好的文件默认是.markdown
格式。
用编辑器打开一篇博客,开头的Front Matter会告诉Jekyll如何处理博客和页面。
1 2 3 4 5 6 7 8 |
|
你可以关闭评论也可以为博客添加标签。如果还在打草稿,可以添加published: false
以免它在生成文件时被发布。你可以添加一个标签也可以添加多个标签:
1 2 3 4 5 6 7 8 9 10 11 |
|
使用Markdown语法(语法说明)来写博客,也可以使用liquid template features 。
1 2 3 |
|
rake preview
是一个很好的功能,在发布博客之前可以先预览一下在页面的效果,进行修正。
Scroll To Top这个网站提供了很多返回页面顶部的Widget。
在source/_includes/custom
文件夹下新建一个scroll_to_top.html
的HTML文件:
1
|
|
接着在source/_layouts/default.html
中引入该文件:
1
|
|
此时博客在右下角添加了一个返回页面顶部的button。
当然也可以在source/javascripts
文件夹下保存使用到的javascript文件,直接在scroll_to_top.html
中调用:
1
|
|
制作属于自己的button。
在博客中插入<!-- more -->
后,在博客首页上,会把该标记之下的博客内容隐藏,点击Read on →
按钮可以看到整篇博客。
在source
文件夹下将favicon.png
替换成自己的图片。注意,需要使用16px×16px的图片,256色或24位,所以图片的内容不要太复杂。
在Github上写博客可以分享知识给其他人,同时也是一个督促自己学习的好方法,毕竟搭建网站需要维护,而Github把这些替你做了,让自己可以专注注意力。当然也请善用,要善对这个平台。
在搭建博客时如果有什么问题可以问我,水平有限,会尽力解答。
参考文献
]]>是一种简单直观的排序算法,我觉得基本上是你面对排序问题时最先想到的方法。在未排序序列中找到最小或是最大的元素,存放到排序序列的起始位置;然后再从剩余的未排序的元素中继续寻找最小或是最大的元素。这样子持续进行下去,直到所有的元素排好顺序。往往最先想到的方法能解决问题,但是不能更好的解决问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。
冒泡排序,也叫sinking sort,是一种简单的排序算法。它重复地遍历要排序的数组,比较每一对相邻的元素,如果它们次序错误,就把它们进行交换。它的名字来自这种相对小的元素“上浮”到数组的顶部的方式。因为它对元素只用到了比较的操作方式,所以是一种比较排序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。
插入排序是最简单的排序算法。它每次排好最终结果数组的一个元素。
它的一般原理是第p次时,将位置p上的元素向左移动,直到它在前p+1个元素中的正确位置被找到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。
希尔排序是对插入排序的一种改进。
插入排序有下面两个性质:
插入排序在对几乎已经排好序的数据进行操作时,效率很高。
插入排序是低效的,因为它每次只能将数据移动一位。
希尔排序通过比较相距一定间隔的元素来工作,每次排序比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一次排序为止。这样一个所用距离的序列叫做增量序列(increment sequence)。
也就是对于这样的一组数:{ 13, 14, 94, 33, 82, 25, 59, 94, 65, 23, 45, 27, 73, 25, 39, 10 },如果我们以距离5开始进行排序,它们看起来是这样的:
也就是说对每列进行了排序,得到了数组{ 10, 14, 73, 25, 23, 13, 27, 94, 33, 39, 25, 59, 94, 65, 82, 45 },可以发现,此时10已经到了正确的位置上。再以距离3对数组进行排序:
最后以距离1进行排序,也就是简单的插入排序了。
目前已知最好的增量序列是Sedgewick提出的,{ 1, 5, 19, 41, 109, … },该序列中的项要么是(9×4i-9×2i+1),要么是(4i-3×2i+1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。
这个算法中基本的操作是把两个已排序的序列合并成一个序列。
归并的操作过程为:
创建一个空序列,使其大小为两个已排序的序列之和;
设定两个计数器,最初的位置分别为两个已经排序序列的起始位置;
比较两个计数器处元素的大小,选择相对小的元素放入空序列中,对应的计数器向前推进一步;
重复步骤3直到某一计数器到达序列尾;
将另一序列中剩下的所有元素复制到空序列中。
该算法是经典的分治(divide-and-conquer)策略,它将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段解得的各答案修补在一起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。
快速排序是实践中一种快速的排序算法。它是一种分治的递归算法。它首先把一个大的数组分成两个小的子数组:较小的数的数组和较大的数的数组。再对这子数组做递归处理。
实现快速排序的步骤:
从数组中任意选择一个元素,称为枢纽元(pivot)。虽然无论选择哪个元素作为pivot都可以完成排序,但是有些选择显然优于其他选择。将第一个元素作为pivot是一种错误的做法。安全的做法是随机选取pivot。最好的做法是选择数组的中值:一般的做法是使用左端、右端和中心位置上的三个元素的中值作为pivot。
对数组进行重新排序,所有比pivot小的数在pivot前面,所有比pivot大的数在pivot后面(相等的可以在任何一边)。经过这样的分区后,pivot就在它最终的位置上了。这个步骤叫做分区操作。
把上述的步骤对含有较小数的子数组和含有较大数的子数组进行递归操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
1 2 3 4 5 6 7 8 9 10 |
|
测试通过。上面的快速排序算法使用左端、右端和中心位置上的三个元素的中值作为pivot。这是比较好的实践。
参考文献
]]>第二本Bob大叔的书。
敏捷开发(Agile Development)
极限编程(eXtreme Programming,简称XP)
概括出如何让软件开发团队具有快速工作、响应变化能力的价值观(value)。也就是敏捷联盟宣言。瞄了一眼名单,看到了Martin Fowler,他的网站有不少干货。
个体和交互胜过过程和工具
可以工作的软件胜过面面俱到的文档
客户合作胜过合同谈判
响应变化胜过遵循计划
于是引出了12条原则
- 我们最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
持续交付。
- 即使到了开发的后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
保持软件结构的灵活性。
- 经常性地交付可以工作的软件,交付的间隔可以从几周到几个月,交付的时间间隔越短越好。
快速迭代。
在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
围绕被激励起来的个人来构建项目。给他们提供所需要的环境和支持,并且信任他们能够完成工作。
在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面的交谈。
工作的软件是首要的进度度量标准。
我的理解是要明确好真正的目标是什么。
- 敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
还是那句话,敏捷项目是马拉松长跑。
不断地关注优秀的技能和好的设计会增强敏捷能力。
简单——使未完成的工作最大化的艺术——是根本的。
我觉得TDD的好处就是可以避免开发的过程中想太多导致范围超出预期。
- 最好的构架、需求和设计出自于自组织的团队。
敏捷团队的成员共同来解决项目中所有方面的问题。
- 每隔一定时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
隔一段时间进行一下Retro。
极限编程(eXtreme Programming,简称XP)是敏捷方法中最著名的一个。它由一系列简单却互相依赖的实践组成。
1.客户作为团队成员
XP团队中的客户是指定义产品的特性并排列这些特性优先级的人或者团队。
2.用户素材
在XP中,我们和客户反复讨论,以获取对于需求细节的理解,但是不去捕获那些细节。
用户素材(user stories)就是正在进行的关于需求谈话的助记符。
3.短交付周期
3.1迭代计划
它由客户根据开发人员确定的预算而选择的一些用户素材组成。
3.2发布计划
它表示了一次较大的交付,通常此次交付会被加入到产品中。
4.验收测试
5.结对编程
6.测试驱动的开发方法
7.集体所有权
结对编程中的每一对都具有拆出(check out)任何模块并对它进行改进的权利。
8.持续集成
9.可持续的开发速度
10.开放的工作空间
11.计划游戏
计划游戏(planning game)的本质是划分业务人员和开发人员之间的职责。业务人员(也就是客户)决定特性(feature)的重要性,开发人员决定实现一个特性所花费的代价。
在每次发布和每次迭代的开始,开发人员基于在最近一次迭代或者最近一次发布中他们所完成的工作量,为客户提供一个预算。客户选择那些所需的成本合计起来不超过该预算的用户素材。
12.简单的设计
12.1考虑能够工作的最简单的事情
12.2你将不需要它
他们开始时假设将不需要那些基础结构。
12.3一次,并且只有一次
极限编程者不能容忍重复的代码。
13.重构
14.隐喻
它是将整个系统联系在一起的全局视图:它是系统的未来景象,是它使得所有单独模块的位置和外观(shape)变得明显直观。如果模块的外观与整个系统的隐喻不符,那么你就知道该模块是错误的。
隐喻通常可以归纳为一个名字系统。这些名字提供了一个系统组成元素的词汇表,并且有助于定义它们之间关系。
这章是对XP中计划游戏的描述。
1.初始探索
在项目开始时,开发人员和客户会尽量确定出所有真正重要的用户素材。
素材的编写会一直持续到项目完成。
开发人员共同对这些素材进行估算。估算是相对的,不是绝对的。
探究、分解和速度
对一个用户素材进行分解或者合并的主要原因,是为了使其大小适于被准确地估算。
为了知道用户素材的绝对大小,需要一个称为速度(velocity)的因子。
通常,花费几天时间去原型化一到两个用户素材来了解团队的速度就足够了。这样的一个原型化过程称为探究(spike)。
2.发布计划
客户挑选在该发布中她们想要实现的素材,并大致确定这些素材的实现顺序。客户不能选择与当前开发速度不符的更多的素材。
3.迭代计划
4.任务计划
开发人员把素材分解成开发任务,一个任务就是一个开发人员能够在4~16小时之内实现的一些功能。
迭代的中点
在这个时间点上,本次迭代中所安排的半数素材应该被完成。如果没有完成,那么团队会设法重新分配没有完成的任务和职责,以保证在迭代结束时能够完成所有的素材。
5.迭代
每两周,本次迭代结束,下次迭代开始。在每次迭代结束时,会给客户演示当前可运行的程序。要求客户对项目程序的外观、感觉和性能进行评价。客户会以新的用户素材的方式提供反馈。
1.测试驱动的开发方法。
程序中的每一项功能都有测试来验证它的操作的正确性。
首先编写测试可以迫使我们使用不同的观察点。
通过首先编写测试,我们就迫使自己把程序设计为可测试的。
首先编写测试的另一个重要效果,是测试可以作为一种无价的文档形式。
有意图的编程(intentional programming)。在实现之前,先在测试中陈述你的意图,使你的意图尽可能地简单、易读。你相信这种简单和清楚会给程序指出一个好的结构。
在编写产品代码之前,先编写测试常常会暴露程序中应该被解耦合的区域。¡¡
为了测试而对模块进行隔离的需要,迫使我们以对整个程序结构都有益的方式对程序进行解耦合。
2.验收测试
作为验证工具来说,单元测试是必要的,但是不够充分。单元测试用来验证系统的小的组成单元应该按照所期望的方式工作,但是它们没有验证系统作为一个整体时工作的正确性。单元测试是用来验证系统中个别机制的白盒测试(white-box tests)。验收测试是用来验证系统满足客户需求的黑盒测试(black-box tests)。
验收测试由不了解系统内部机制的人编写。客户可以直接或者和一些技术人员(可能是QA人员)一起来编写验收测试。验收测试是程序,因此是可以运行的。然而,通常使用专为应用程序的客户创建的脚本语言来编写验收测试。
验收测试是关于一项特性(feature)的最终的文档。
此外,首先编写验收测试的行为对于系统的构架方面具有深远的影响。
3.结论
测试套件运行起来越简单,就会越频繁地运行它们。测试运行得越多,就会越快地发现和那些测试的任何背离。
单元测试和验收测试都是一种文档形式,那样的文档是可以编译和执行的;因此,它是准确和可靠的。此外,编写测试所使用的语言是明确的,并且它们的观看者使这些语言非常易读。
测试最重要的好处就是它对于构架和设计的影响。
在Martin Fowler的名著《重构》一书中,他把重构(Refactoring)定义为:“……在不改变代码外在行为的前提下对代码做出修改,以改进代码的内部结构的过程。”
每一个软件模块都具有三项职责。第一个职责是它运行起来所完成的功能。……第二个职责是它要应对变化。……第三个职责是要和阅读它的人进行沟通。
重构的目的,正像在本章中描述的,是为了每天清洁你的代码。
图示有时是不需要的。何时不需要呢?在创建了它们而没有验证它们的代码就打算去遵循它们时,图示就是无意的。画一幅图来探究一个想法是没有错的。然而,画一幅图后,不应该假定该图就是相关任务的最好设计。你会发现最好的设计是在你首先编写测试,一小步一小步前进时逐渐形成的。
形象地展现了结对编程、TDD以及重构的过程。
多一句嘴,之前一直在诺基亚手机上玩bowling的游戏,看完这章知道它的记分规则了。
在每次迭代中,团队改进系统设计,使设计尽可能适合于当前系统。团队不会花费许多时间去预测未来的需求和需要,也不会试图在今天就构建一些基础结构去支撑那些他们认为明天才会需要的特性。他们更愿意关注当前的系统结构,并使它尽可能地好。
拙劣设计的症状
僵化性(Rigidity)
脆弱性(Fragility)
牢固性(Immobility)
粘滞性(Viscosity)
不必要的复杂性(Needless Complexity)
不必要的重复(Needless Repetition)
晦涩性(Opacity)
原则
单一职责原则(The Single Responsibility Principle,简称SRP)
开放-封闭原则(The Open-Close Principle,简称OCP)
Liskov替换原则(The Liskov Substitution Principle,简称LSP)
依赖倒置原则(The Dependency Inversion Principle,简称DIP)
接口隔离原则(The Interface Segregation Principle,简称ISP)
这就是面向对象设计中所说的SOLID。
软件项目的设计是一个抽象的概念。它和程序的概括形状(shape)、结构以及每一个模块、类和方法的详细形状和结构有关。可以使用许多不同的媒介(media)去描绘它,但是它最终体现为源代码。最后,源代码就是设计。
当软件出现下面任何一种气味时,就表明软件正在腐化。
1.僵化性(Rigidity)
僵化性是指难以对软件进行改动,即使是简单的改动。如果单一的改动会导致有依赖关系的模块中的连锁改动,那么设计就是僵化的。必须要改动的模块越多,设计就越僵化。
2.脆弱性(Fragility)
脆弱性是指,在进行一个改动时,程序的许多地方就可能出现问题。
3.牢固性(Immobility)
牢固性是指,设计中包含了对其他系统有用的部分,但是要把这些部分从系统中分离出来所需要的努力和风险是巨大的。
4.粘滞性(Viscosity)
粘滞性有两种表现形式:软件的粘滞性和环境的粘滞性。
当面临一个改动时,开发人员常常发现会有多种改动的方法。其中,一些方法会保持设计:而另外一些会破坏设计(也就是生硬的手法)。当那些可以保持系统设计的方法比那些生硬手法更难应用时,就表明设计具有高的粘滞性。做错误的事情是容易的,但是做正确的事情却很难。
当开发环境迟钝、低效时,就会产生环境的粘滞性。
5.不必要的复杂性(Needless Complexity)
如果设计中包含有当前没有用的组成部分,它就含有不必要的复杂性。
6.不必要的重复(Needless Repetition)
当同样的代码以稍微不同的形式一再出现时,就表示开发人员忽视了抽象。
7.晦涩性(Opacity)
晦涩性是指模块难以理解。
在非敏捷环境中,由于需求没有按照初始设计预见的方式进行变化,从而导致了设计的退化。
设计必须要保持干净、简单,并且由于源代码是设计最重要的表示,所以它同样要保持干净。
敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时间都尽可能的简单、干净以及富有表现力。
就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。
在SRP中,我们把职责定义为“变化的原因”(a reason for change)。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。
SRP是所有原则中最简单的之一,也是最难正确运用的之一。我们会自然地把职责结合在一起。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。
开放——封闭原则(The Open-Closed Principle,简称OCP)。
软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的。
遵循开发——封闭原则设计出的模块具有两个主要的特征。它们是:
“对于扩展是开放的”(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。
“对于更改是封闭的”(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的.jar文件,都无需改动。
在C++、Java或者其他任何的OOPL中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这一组任意个可能的行为则表现为可能的派生类。
模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,所以它对于更改可以是关闭的。同时,通过从这个抽象体派生,也可以扩展此模块的行为。
STRATEGY模式
Template模式
一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。
既然不可能完全封闭,那么就必须有策略地对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。
如果我们决定接受第一颗子弹,那么子弹到来的越早、越快就对我们越有利。
开发人员应该仅仅对程序中呈现出频繁变化的那些部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。
子类型(subtype)必须能够替换掉它们的基类型(base type)。
对于LSP的违反常常会导致以明显违反OCP的方式使用运行时类型辨别(RTTI)。这种方式常常是使用一个显式的if语句或者if/else链去确定一个对象的类型,以便于可以选择针对该类型的正确行为。
继承是IS-A(“是一个”)关系。也就是说,如果一个新类型的对象被认为和一个已有类的对象之间满足IS-A关系,那么这个新对象的类应该从这个已用对象的类派生。
LSP清楚地指出,OOD中IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。
基于契约设计(Design By Contract,简称DBC)。使用DBC,类的编写者显式地规定针对该类的契约。客户代码的编写者可以通过该契约获悉可以依赖的行为方式。契约是通过为每个方法声明的前置条件(preconditions)和后置条件(postcondition)来指定的。要使一个方法得以执行,前置条件必须要为真。执行完毕后,该方法要保证后置条件为真。
在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件。
也可以通过编写单元测试的方式来指定契约。单元测试通过彻底的测试一个类的行为来使该类的行为更加清晰。
不应该轻易放弃对于LSP的遵循。总是保证子类可以替代它的基类是一个有效的管理复杂性的方法。
可以把两个类的公共部分提取出来作为一个抽象基类。
有一些简单的启发规则可以提供一些有关违反LSP的提示。
派生类中的退化函数
从派生类中抛出异常。如果基类的使用者不期望这些异常,那么把它们添加到派生类的方法中就会导致不可替换性。
依赖倒置原则(DIP)
高层模块不应该依赖于低层模块。二者都应该依赖于抽象。
抽象不应该依赖于细节。细节应该依赖于抽象。
一个设计良好的面向对象的程序,其依赖程序结构相对于传统的过程式方法设计的通常结构而言就是被“倒置”了。
无论如何高层模块都不应该依赖于低层模块。
如果高层模块独立于低层模块,那么高层模块就可以非常容易地被重用。该原则是框架(framework)设计的核心原则。
每个较高层次都为它所需要的服务声明一个抽象接口,较低的层次实现了这些抽象接口,每个高层类都通过该抽象接口使用下一层,这样高层就不依赖于低层。低层反而依赖于在高层中声明的抽象服务接口。
一个稍微简单但仍然非常有效的对于DIP的解释,是这样一个简单地启发式规则:“依赖于抽象。”
根据这个启发式规则,可知:
任何变量都不应该持有一个指向具体类的指针或者引用
任何类都不应该从具体类派生
任何方法都不应该覆写它的任何基类中的已经实现了的方法
我们在应用程序中所编写的大多数具体类都是不稳定的。我们不想直接依赖于这些不稳定的具体类。通过把它们隐藏在抽象接口的后面,可以隔离它们的不稳定性。
另一方面,如果看得更远一点,认为是由客户类来声明它们需要的服务接口,那么仅当客户需要时才会对接口进行改变。这样,改变实现抽象接口的类就不会影响到客户。
动态多态性
静态多态性
使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。这是糟糕的,因为这样会使策略受到细节改变的影响。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。
如果类的接口不是内聚的(cohesive),就表示该类具有“胖”的接口。换句话说,类的“胖”接口可以分解成多组方法。每一组方法都服务于一组不同的客户程序。这样,一些客户程序可以使用一组成员函数,而其他客户程序可以使用其他组的成员函数。
不应该强迫客户依赖于它们不用的方法。
一个对象的客户不是必须通过该对象的接口去访问它,也可以通过委托或者通过该对象的基类去访问它。
胖类(fat class)会导致它们的客户程序之间产生不正常的并且有害的耦合关系。当一个客户程序要求该胖类进行一个改动时,会影响到所有其他的客户程序。因此,客户程序应该仅仅依赖于它们实际调用的方法。通过把胖类的接口分解为多个特定于客户程序的接口,可以实现这个目标。
命令模式。
大多数类都是一组方法和相应的一组变量的组合。COMMAND模式不是这样的。它只是封装了一个没有任何变量的函数。
可以创建一个简单的文本文件来描述Sensor和Command之间的绑定关系。初始化程序可以读取该文件,并构建出对应的系统。这样,系统中的连接关系可以完全在程序以外确定,并且对它的调整也不会引起重新编译。
通过对命令(command)概念的封装,该模式解除了系统的逻辑互联关系和实际连接的设备之间的耦合。这是一个巨大的好处。
另外一个COMMAND模式的常见用法是创建和执行事务操作(Transactions)。
这给我们带来的好处在于很好地解除了从用户获取数据的代码、验证并操作数据的代码以及业务对象本身之间的耦合关系。
我们也以一种不同的方式解耦了验证和执行代码。一旦获取了数据,就没有理由要求验证和执行方法立即被调用。可以把事务操作对象放在一个列表中,以后再进行验证和执行。
给COMMAND模式增加了undo()方法。显而易见,如果Command派生类的do()方法可以记住它所执行的操作的细节,那么undo方法就可以取消这些操作,并把系统恢复到原先的状态。
活动对象模式。
ACTIVE OBJECT模式是实现多线程控制的一项古老的技术。……遍历链表,执行并去除每个命令。……如果链表中的一个Command对象会克隆自己并把克隆对象放到链表的尾部……这个链表永远不会为空,函数永远不会返回。……如果所等待的事件没有发生,它只是把自己放回。
采用该技术的变体(variations)去构建多线程系统已经是并且将会一直是一个很常见的实践。这种类型的线程被称为run-to-completion任务(RTC),因为每个Command实例在下一个Command可以运行之前就运行完成了。RTC的名字意味着Command实例不会阻塞。
模版方法模式。
继承非常容易被过度使用,而且过度使用的代价是非常高的。所以我们减少了对继承的使用,常常使用组合或者委托来代替它。
TEMPLATE METHOD模式使用继承来解决问题,而STRATEGY模式使用的则是委托。
编写过的所有程序,其中许多可能都具有如下的基本主循环结构:首先进行初始化;接着进入主循环;在主循环中完成需要做的工作;最后,一旦完成了工作,程序就退出主循环,并且在程序终止前做些清除工作。
TEMPLATE METHOD模式把所有通用代码放入一个抽象基类(abstract base class)的实现方法中。这个实现方法完成了这个通用算法,但是将所有的实现细节都交付给该基类的抽象方法。
通过继承基类,只需要实现基类中的抽象方法即可。
设计模式是很好的东西。它们可以帮助解决很多设计问题。但是它们的存在并不意味着必须要经常使用它们。
TEMPLATE METHOD模式展示了面向对象编程中诸多经典重用形式中的一种。其中通用算法被放置在基类中,并且通过继承在不同的具体上下文中实现该通用算法。但是这项技术是有代价的。继承是一种非常强的关系。派生类不可避免地要和它们的基类绑定在一起。
策略模式。
STRATEGY模式使用了一种非常不同的方法来倒置通用算法和具体实现之间的依赖关系。
不是将通用的应用算法放进一个抽象基类中,而是将它放进一个具体类中。把通用算法必须要调用的抽象方法定义在一个借口中。从这个接口中派生出类,并把它传给放有通用算法的具体类。之后,该类就可以把具体工作委托给这个接口去完成。
STRATEGY模式比TEMPLATE METHOD模式多提供了一个额外的好处。尽管TEMPLATE METHOD模式允许一个通用算法操纵多个可能的具体实现,但是由于STRATEGY模式完全遵循DIP原则,从而允许每个具体实现都可以被多个不同的通用算法操纵。不过要以一些额外的复杂性、内存以及运行时间开销作为代价。
外观模式。
这两个模式都把某种策略(policy)施加到另外一组对象上。FACADE模式从上面施加策略,而MEDIATOR模式则是从下面施加策略。FACADE模式的使用是明显且受限的,而MEDIATOR模式的使用则是不明显且不受限制的。
当想要为一组具有复杂且全面的接口的对象提供一个简单且特定的接口时,可以使用FACADE模式。它对用户隐藏了所有的复杂性。
中介者模式。
MEDIATOR模式把它的策略施加在那些对象上,而无需它们的允许或者知晓。
如果策略涉及范围广泛并且可见,那么可以使用FACADE模式从上面施加该策略。另一方面,如果策略隐蔽并且有针对性,那么MEDIATOR模式是更好的选择。Facades通常是约定的关注点。每个人都同意去使用该facade而不是隐藏于其下的对象。另一方面,Mediator则对用户是隐藏的。它的策略是既成事实的而不是一项约定事务。
单例模式。
然而有一些类,它们应该只有一个实例。
实现SINGLETON模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
SINGLETON模式的好处:跨平台;适用于任何类;可以透过派生创建;延迟求值(Lazy evaluation)。
MONOSTATE模式是另外一种获取对象单一性的方法。
使多个实例表现得像一个对象,只需要它们共享相同的变量。只要把所有的变量都变成静态变量即可。
这两个模式之间的区别,在于一个关注行为,而另一个关注结构。SINGLETON模式强制结构上的单一性。它防止创建出多个对象实例。相反,MONOSTATE模式则强制行为上的单一性,而没有强加结构方面的限制。
MONOSTATE模式的好处:透明性;可派生性;多态性。
空对象模式。
通常,该模式会消除对null进行检查的需要,并且有助于简化代码。
使用该模式,我们可以确保函数总是返回有效的对象,即使在它们失败时也是如此。这些代表失败的对象“什么也不做”。
(先挖坑,马上填)
]]>git daemon
,可以很方便地分享本地Git Repo的读写权限。
git daemon
启动了一个TCP服务,通过git://
协议分享Git Repo,默认的端口号是9418
,它会监听该端口,提供服务。但是需要注意的是,这种方式是不安全的,通常只在安全的值得信赖的网络中使用。如果想要安全的解决方案,应该使用SSH协议。
步骤是:
1.进入想要分享的Project目录:
1
|
|
2.clone裸仓库:
1
|
|
完成后,git会在当前目录下创建一个名为Project.git的目录。
3.在Project.git目录下创建一个名为git-daemon-export-ok的文件:
1
|
|
这是为了让daemon知道只分享该Git Repo,也可以在运行git daemon
时加上--export-all
,这样所有的Git Repo都会被分享。
4.将Project.git目录移动到你想要分享的目录中:
1
|
|
5.运行git daemon
命令:
1
|
|
这样就可以clone本地的Git Repo了:
1
|
|
-–base-path=
,所有的请求都是该path的相对链接,运行上面的git clone
命令时,daemon会理解path为/Shared/Project.git
。
通过上述步骤,只能通过git://
协议获得clone的权限,如果需要获得push的权限,可以在运行git daemon
命令时加上--enable=receive-pack
,或者进入Shared/Project.git
目录,输入:
1 2 |
|
git会在当前目录下的config文件中添加以下配置:
1 2 |
|
这时就可以允许其他用户拥有push的权限。默认上该权限是关闭的,因为使用的协议并未进行验证,也就是说任何人可以push任何东西到Git Repo中,包括清除它。这意味着只能在一个封闭的局域网中设置它。
参考文献
]]>equals()
和hashCode()
这两个方法的理解。
在Object
类中定义了equals()
和hashCode()
这两个方法。Object
类是类继承结构的基础,所以是每一个类的父类。所有的对象,包括数组,都实现了在Object
类中定义的方法。
equals()
方法是用来判断其他的对象是否和该对象相等,它的性质有:
自反性(reflexive)。对于任意不为null
的引用值x,x.equals(x)
一定是true
。
对称性(symmetric)。对于任意不为null
的引用值x
和y
,当且仅当x.equals(y)
是true
时,y.equals(x)
也是true
。
传递性(transitive)。对于任意不为null
的引用值x
、y
和z
,如果x.equals(y)
是true
,同时y.equals(z)
是true
,那么x.equals(z)
一定是true
。
一致性(consistent)。对于任意不为null
的引用值x
和y
,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)
要么一致地返回true
要么一致地返回false
。
对于任意不为null
的引用值x
,x.equals(null)
返回false
。
对于Object
类来说,equals()
方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null
的引用值x
和y
,当且仅当x
和y
引用的是同一个对象,该方法才会返回true
。
需要注意的是当equals()
方法被override时,hashCode()
也要被override。按照一般hashCode()
方法的实现来说,相等的对象,它们的hash code一定相等。
hashCode()
方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。
它的性质是:
在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()
方法,该方法必须始终如一返回同一个integer。
如果两个对象根据equals(Object)
方法是相等的,那么调用二者各自的hashCode()
方法必须产生同一个integer结果。
并不要求根据equals(java.lang.Object)
方法不相等的两个对象,调用二者各自的hashCode()
方法必须产生不同的integer结果。然而,程序员应该意识到对于不同的对象产生不同的integer结果,有可能会提高hash table的性能。
大量的实践表明,由Object
类定义的hashCode()
方法对于不同的对象返回不同的integer。
来看一个例子。创建一个Book
类:
1 2 3 4 5 6 7 8 9 |
|
Book
类有两个非常基础的属性:书名name
和作者author
。现在,来比较两本书:
1 2 3 4 5 6 7 8 9 |
|
测试没有通过。虽然两本书有着同样的书名和作者,但是它们是两本“不同”的书。
改写equals()
方法看起来非常简单,但是有许多改写的方式会导致错误,并且后果非常严重。要避免问题最重要的办法是不改写equals()
方法。
那么什么时候应该改写Object.equals
呢?当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念),而且超类也没有改写equals()
以实现期望的行为,这时需要改写equals()
方法。这通常适合于value object。
再说一遍,在每个改写了equals()
方法的类中,必须要改写hashCode()
方法。如果不这样做,就会违反Object.hashCode
的通用约定,从而导致该类无法与所有基于hash的集合类结合在一起正常运作,这样的集合类包括HashMap、HashSet和HashTable。
需要注意的是
1 2 3 |
|
这个hashCode()
方法是合法的,因为相等的对象总是具有同样的散列码。但是它使得每一个对象都具有同样的散列码。因此,每个对象都被映射到同一个散列桶中,从而散列表被退化为链表。对于规模很大的散列表而言,这关系到散列表能否正常工作。
一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。
如果一个类是非可变的,并且计算散列码的代价也比较大,那么你应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。
不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。
我使用的是IntelliJ,使用^
+N
快捷键可以很好的overrideequals()
和hashCode()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
这个时候再跑前面的测试就会发现测试通过了。
那么如果我没有overridehashCode()
会发生什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
这个时候你会发现之前的测试依然通过,但是如果换一下测试:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
逻辑上,我用相同的书在借书表里查询,应该返回对应的人名。但是这条测试并没有通过,事实上返回的是null
。因为没有改写hashCode()
方法,从而导致两个相等的实例具有不相等的散列码,违反了hashCode()
的规定。put()
方法把LiLei借出的书对象存放在一个散列桶中,get()
方法会在另一个散列桶中查找LiLei所借的书对象,返回null
也就不出意外了。要想修正,只要提供适当的hashCode()
方法就可以了。
参考文献:
]]>请你把这本书看成我的错误大全,它记录了我干过的所有蠢事;也请你把这本书当成一份指引,让它带你绕开我曾经走过的弯路。
“专业主义”有很深的含义,它不但象征着荣誉与骄傲,而且明确意味着责任与义务。
实际上,专业主义的精髓就在于将公司利益视同个人利益。
“希波克拉底誓言”
“敬禀醫神阿波羅、阿斯克勒庇俄斯、許癸厄亞、帕那刻亞,及天地諸神聖鑒之,鄙人敬謹宣誓:
余願盡己之能力與判斷力之所及,矢守此約。凡授余藝者:余敬如父母,為終身同甘共苦之侶;倘有急需余必接濟。視彼兒女,猶余手足,如欲受業,余無償、無條件傳授之。凡余之所知,無論口授、書傳俱傳之吾子、吾師之子、及立誓守此約之生徒,此外不傳他人。
余願盡己之能力與判斷力之所及,恪守為病家謀福之信條,並避免一切墮落害人之敗行,余必不以毒物藥品與他人,並不作此項之指導,雖人請求亦必不與之,尤不為婦人施墮胎之術。余願以此純潔神聖之心,終身執行余之職務。至於手術,另待高明,余不施之,遇結石患者亦然,惟使專匠為之。
無論何適何遇,逢男或女,民人奴隸,余之唯一目的,為病家謀福,並檢點吾身,不為種種墮落害人之敗行,尤不為誘姦之事。凡余所見所聞,不論有無業務之牽連,余以為不應洩漏者,願守口如瓶。
倘余嚴守上述之誓詞,願神僅僅使余之生命及醫術,得無上之光榮;余苟違誓,天地鬼神共殛之!” Wikipedia
所谓专业人士,就是能对自己犯下的错误负责的人,哪怕那些错误实际上在所难免。所以,雄心勃勃的专业人士们,你们要练习的第一件事就是“道歉”。道歉是必要的,但还不够。你不能一而再、再而三地犯相同的错误。
失误率永远不可能等于零,但你有责任让它无限接近零。
什么样的代码是有缺陷的呢?那些你没把握的代码都是!
你怎么知道代码能否常运行呢?很简单,测试!
实行自动化测试。
你写的每一行代码都要测试。
但是有些代码不是很难测试吗?是的,但之所以很难测试,是因为设计时就没考虑如何测试。唯一的解决办法就是要设计易于测试的代码,最好是先写测试,再写要测的代码。
你的自动化测试至少要能够让你知道,你的系统很有可能通过QA的测试。
结构良好的代码更灵活。
所有软件项目的根本指导原则是,软件要易于修改。
如果你希望自己的软件灵活可变,那就应该时常修改它!
“无情重构”,我把它叫作“童子军训练守则”:对每个模块,每检入一次代码,就要让它比上次检出时变得更为简洁。每次读代码,都别忘了进行点滴的改善。
测试就是帮助你检查每次改动是否会与开始时的需求相违背。
职业发展是你自己的事。雇主没有义务确保你在职场能够立于不败之地,也没义务培训你,送你参加各种会议或给你买各种书籍充电。这些都是你自己的事。将自己的职业发展寄希望于雇主的软件开发人员将会很惨。
雇主出了钱,你必须付出时间和精力。
不能赞同更多。
近50年来,各种观点、实践、技术、工具与术语在我们这一领域层出不穷。你对这些了解多少呢?如果想成为一名专业开发者,那你就得对其中的相当一大部分有所了解,而且要不断扩展这一知识面。
设计模式。必须能描述GOF书中的全部24种模式,同时还要有POSA书中的多数模式的实战经验。
设计原则。必须了解SOLID原则,而且要深刻理解组件设计原则。
方法。必须理解XP、Scrum、精益、看板、瀑布、结构化分析及结构化设计等。
实践。必须掌握测试驱动开发、面向对象设计、结构化编程、持续集成和结对编程。
工件。必须了解如何使用UML图、DFD图、结构图、Petri网络图、状态迁移图表、流程图和决策图。
读书,看相关文章,关注博客和微博,参加技术大会,访问用户群,多参与读书与学习小组。不懂就学,不要畏难。
业精于勤。
练习,指的是在日常工作之余专门练习技能,以期自我提升。
学习的第二个最佳方法是与他人合作。
想迅速牢固地掌握某些事实和观念,最好的方法就是与由你负责的人交流这些内容。
分享知识时也是这样,自己事先需要好好备课。
每位专业软件开发人员都有义务了解自己开发的解决方案所对应的业务领域。
每次开发系统,都应该站在雇主的角度来思考,确保开发的功能真正能满足雇主的需要。
他从不会嘲讽别人,自作自受时他会接受别人的嘲讽。反之,他则会一笑了之。
专业人士敢于说明真相而不屈从于权势。
我的个人经验告诉自己,要做出艰难决定的时候,存在对抗角色间的冲突于此是最为有利的。
最要说“不”的是那些高风险的关键时刻。越是关键时刻,“不”字就越具价值。
没有“试试看”这回事。
许诺“尝试”,就意味着你承认自己之前未尽全力,承认自己还有余力可施。
如果你此前并未有所保留,如果你没有新方案,如果你不会改变你的行为,如果你对自己原先的估计有充分的自信,那么,从本质上讲,承诺“尝试”就是一种不诚实的表现。
很多时候也许是避免冲突,大家习惯上说“试试看”,如果对自己自信,而且有着负责任的态度,更应该对一些很难做到的需求说“不”。
如果一列载货列车向大家冲过来,而只有你一人有所察觉,你可以轻轻抽身退到轨道外,眼看其他人被车碾过,也可以大喊:“车!车来了!快离开!”
有时候,获取正确决策的唯一途径,便是勇敢无畏地说出“不”字。
成为英雄及“解决问题”的诱惑诚然巨大,只是我们要明白,委屈专业原则以求全,并非问题的解决之道。舍弃这些原则,只会制造出更多的麻烦。
口头上说。心里认真。付诸行动。
做出承诺,包含三个步骤。
(1) 口头上说自己将会去做。
(2) 心里认真对待做出的承诺。
(3) 真正付诸行动。
在承诺做某事时,应当留意自己的用词,因为这些用词透露了我们对待承诺的认真程度。
你对自己将会做某件事做了清晰的事实陈述,而且还明确说明了完成期限。
“试试”,则是“可能做得到,也可能做不到”的意思。
如果是专业开发人员,就不会放弃底线。
多年经验告诉我们,打破这些纪律和原则,必然会拖慢进度。
身为专业开发人员,有责任根据标准规范自身工作。
专业人士对自己的能力极限了如指掌。他们十分清楚自己还能保持效率加班多长时间,也非常明白要付出的代价。
具备“出错感知能力”,说明你已经能够非常迅速地获得反馈,能够更为快速地从错误中学习。
要精熟掌握每项技艺,关键都是要具备“信心”和“出错感知”能力。
相比其他类型的活动,编码要求更加聚精会神。因为在编码时你必须平衡互相牵制的多种因素。
(1) 首先,代码必须能够正常工作。必须理解当前要解决的是什么问题以及该如何解决。必须确保编写的代码忠实遵循解决方案。必须管理好解决方案的每一处细节,并且使语言、平台、现有架构以及当前系统的所有问题和平共处。
(2) 代码必须能够帮你解决客户提出的问题。
(3) 代码必须要能和现有系统结合的天衣无缝。
(4) 其他程序员必须能读懂你的代码。
如果感到疲劳或者心烦意乱,千万不要编码。
疲劳的时候,千万不要写代码。奉献精神和职业素养,更多意义上指要遵循纪律原则而非成为长时间工作的工作狂。要确保自己已经将睡眠、健康和生活方式调整到最佳状况,这样才能做到在每天的8小时工作时间内全力以赴。
理想情况下,应该使用个人时间去解决个人问题。
专业开发人员善于合理分配个人时间,以确保工作时间段中尽可能富有成效。
这是程序员在编写代码时会进入的一种意识高度专注但思维视野却会收拢到狭窄的状态。
避免进入流态区。这种意识状态并非真的极为高效,也绝非毫无错误。这其实只是一种“浅层冥想”状态,在这种状态下,为了追求所谓的速度,理性思考的能力会下降。
结对编程最大的一个好处在于,结对中的任一方都不可能进入流态区。流态区是一种隔绝沟通的状态,而结对则要求激励持续地进行沟通。
在听音乐时无法写好代码。
结对是用以应对中断的一种好方法。
另一种很有帮助的方法便是采用TDD。
有一个很简单的好办法可以解决这个问题。这个方法便是:找一个搭档结对编程。
结对带来的主要好处是它能够帮我重新激活思维。
还有其他一些事物可以让我免于陷入阻塞状态。“创造性输出”依赖于“创造性输入”。
不管是否采纳TDD或其他一些同等效果的实践,衡量你是否是一名专业人士的一个重要方面,便是看你是否能将调试时间尽量降到最低。绝对的零调试时间是一个理想化的目标,无法达到,但要将之作为努力方向。
软件开发是一场马拉松,而不是短跑冲刺。
管理延迟的诀窍,便是早期检测和保持透明。
根据目标定期衡量进度,使用三个考虑到多种因素的期限:乐观预估、标称预估、悲观预估。尽量严守这三个时间点。
期望会破坏项目进度表。
不要经受不住诱惑盲目冲刺。
不应该采用额外加班加点工作的方案,除非以下三个条件都能满足:(1)你个人能挤出这些时间;(2)短期加班,最多加班两周;(3)你的老板要有后备预案,以防万一加班措施失败。
在程序员所能表现的各种不专业行为中,最糟糕的是明知道还没有完成任务却宣称已经完成。
可以通过创建一个确切定义的“完成”标准来避免交付失误。最好的方法是让业务分析师和测试人员创建一个自动化的验收测试,只有完全通过这些验收测试,开发任务才能算已经完成。
即使你的技能格外高超,也肯定能从另外一名程序员的思考与想法中获益。
辅导缺乏经验的程序员是那些经验丰富的程序员的职责。
同样道理,向资深导师寻求辅导也是年轻程序员的专业职责。
(1)在编好失败单元测试之前,不要编写任何产品代码。
(2)只要有一个单元测试失败了,就不要再写测试代码;无法通过编译也是一种失败情况。
(3)产品代码恰好能够让当前失败的单元测试成功通过即可,不要多写。
有不少报告和研究称TDD能够显著降低缺陷。
拥有一套值得信赖的测试,便可完全打消对修改代码的全部恐惧。
单元测试即是文档。
测试代码的一个问题是必须隔离出待测试的代码。
因此,遵循三项法则并且测试先行,便能够产生一种驱动力,促使你做出松耦合的设计。
TDD是专业人士的选择。
TDD大法好。在学写代码的开始,就是想着怎么实现怎么写,写出来的代码很糟糕。后来在别人的帮助下系统的学习敏捷的一套流程,发现TDD确实很实用。持续集成的局限可能是你要多一台机子去跑CI,TDD的局限我目前还没有注意到,这也是自己功力尚浅的原因。会在后面一直实践下去。
无论是搏斗还是编程,速度都来源于练习。
The Coding Dojo
就像习武的人那样,有时候一群程序员聚在一起练习,也有些时候是独自练习。
Kata
编程卡塔也是一整套敲击键盘和鼠标的动作,用来模拟编程问题的解决过程。
编程卡塔的最终目标,也是逐步练习以达到纯熟。反复的练习会训练大脑和手指如何动作和反应。
要学习热键和导航操作,以及测试驱动开发、持续集成之类的方法,找整套的卡塔来练习都是相当有效的。更重要的是,它特别有利于在潜意识中构筑通用的问题与解决方案间的联系。
Wasa
瓦萨基本可以说是两个人的卡塔。
两个人选择一个卡塔,或者一个简单问题,一个人写单元测试,另一个人写程序通过单元测试,然后交换角色。
Randori
在自由练习中,屏幕被投影到墙上,一个人写测试,然后坐下来,另一个人写程序通过测试,再写下一个测试。桌子边的人一个个轮流接下去,或者有兴趣的人可以自己排队参加。
保持不落伍的一种方法是为开源项目贡献代码。
职业程序员用自己的时间来练习。
之前在TWU把三种Dojo的方式都练习过,但是当时读书少,不知道它们的名字,只是觉得很有意思,也了解到不同人的思考问题的方式。现在看来茅塞顿开。喜欢书里面提到的一句话,“老板的职责不包括避免你的技术落伍,也不包括为你打造一份好看的履历”。嗯,更多的练习,让自己进步。
开发方与业务方之间最常见的沟通是关于需求的。
做业务的人和写程序的人都容易陷入一个陷阱,即过早进行精细化。
避免过早精细化的方法是尽可能地推迟精细化。
但是,这可能造成另一个问题:迟来的模糊性。
“需求文档中的每一点模糊之处,都对应着业务方的一点分歧。”
业务方与开发方合作编写的测试,其目的在于确定需求已经完成。
完成意味着所有的代码都写完了,所有的测试都通过了,QA和需求方已经认可。
专业开发人员会根据自动化的验收测试来定义需求。
验收测试的目的是沟通、澄清、精确化。
验收测试都应当自动进行。
接触过Cucumber的测试,但是没有实现过,应该尝试一下,写写自动化的验收测试。
不要把它们(测试)看作额外的工作,而应当看成节省时间和金钱的办法。这些测试可以避免你的开发误入歧途,也可以帮你确认自己已经完工。
通常,业务分析员测试“正确路径”,以证明功能的业务价值;QA则测试“错误路径”、边界条件、异常、例外情况。
在敏捷项目中,只有在选定了下一轮迭代(Iteration)或当前冲刺(Sprint)所需要的功能之后,才编写测试。
迭代开始的第一天,就应当准备好最初的几项验收测试。然后每天都应当完成一些验收测试,到迭代的中间点,所有的测试都应当准备完毕。
实现某项功能的代码,应该在对应的验收测试完成后开始。开发人员运行这些验收测试,观察失败的原因,将验收测试与系统联系起来,然后实现需要的功能,让测试通过。
身为专业开发人员,与编写测试的人协商并改进测试是你的职责。
验收测试不是单元测试。单元测试是程序员写给程序员的,它是正式的设计文档,描述了底层结构及代码的行为。
验收测试是业务方写给业务方的。它们是正式的需求文档,描述了业务方认为系统应该如何运行。
它们的主要功能其实不是测试,测试只是它们的附属职能。单元测试和验收测试首先是文档,然后才是测试。
通过GUI背后的API来测试业务逻辑。
有些验收测试规定了GUI自身的行为。这些测试必须通过GUI。但是,这些测试并不是测试业务逻辑的,所以不需要业务规则关联到GUI。最好把GUI和业务规则解耦合,在测试GUI时,用测试桩替代业务规则。
应当尽可能地减少GUI测试。
保持持续集成系统的时刻运行是非常重要的。
在持续集成系统里,失败的集成应该视为紧急情况,也就是“立刻中止”型事件。
我们最怕的就是在周五下午提交代码,挂的死去活来的CI是每个人的噩梦。
要解决开发方和业务方沟通问题,我所知道的唯一有效的办法就是编写自动化的验收测试。
每个专业的开发团队都需要一套好的测试策略。
对QA找到的每一个问题,开发团队都应该高度重视、认真对待。应该反思为什么会出现这种错误,并采取措施避免今后重犯。
QA在团队中要扮演的便是需求规约定义着(specifier)和特性描述者(characterizer)。
QA的任务便是和业务人员一起创建自动化验收测试,作为系统真正的需求规约文档。每轮迭代中,他们都可以从业务人员那里收集需求,将之翻译为向开发人员描述系统行为的测试。通常,业务人员编写针对正常路径的测试(happy-pathtest),而由QA编写针对极端情况(corner)、边界状态(boundary)和异常路径(unhappy-pathtest)的测试。
QA的另一项任务是遵循探索式测试的原则,描述系统运行中的真实情况,将之反馈给开发人员和业务人员。
Unit tests
在金字塔底部是单元测试,这些测试由程序员使用与系统开发相同的语言来编写,供程序员自己使用。
Component tests
组件测试是验收测试的一种。通常,它们是针对系统的各个组件而编写的。系统的组件封装了业务规则,因此,对这些组件的测试便是对其中业务规则的验收测试。
在组件测试中,需要使用合适的模拟(mocking)或测试辅助(test-doubling)技术,解开与系统的其他组件的耦合。
组件测试由QA和业务人员编写,开发人员提供辅助。它们需要在FitNesse、JBehave或Cucumber等组件测试环境下编写。其目的是让不具备编写测试能力的业务人员也能理解这些测试。
它们更主要测试的是成功路径的情况,以及一些明显的极端情况、边界状态和可选路径。大多数的异常路径是由单元测试来覆盖测试的。
Integration tests
这些测试将组件装配成组,测试它们彼此之间是否能正常通信。
集成测试是编排性(choreography)测试。它们并不会测试业务规则,而是主要测试组件装配在一起是否协调。
在这个层次上,也许已经可以进行性能测试和吞吐率测试了。
System tests
这些测试是针对整个集成完毕的系统来运行的自动化测试,是最终的集成测试。它们不会直接测试业务规则,而是测试系统是否已正确组装完毕,以及系统各个组成部件之间是否能正确交互。在这个层次的测试集中,应该包含吞吐率测试和性能测试。
这是需要人工介入、敲击键盘、盯牢屏幕的测试。
探索式测试不是要证明每条业务规则、每条运行路径都正确,而是要确保系统在人工操作下表现良好,同时富有创造性地找出尽可能多的“古怪之处”。
这下更清楚不同测试的目的了。觉得有时间要把Kent Beck的Test Driven Development: By Example好好地读一下。作者由Kent手把手教TDD,羡慕。
这里有必要说一句,之前用TDD更多的是写Unit tests,所以对TDD的使用可能有些混淆了概念。TDD测试的是Features, 不是“Units”,有时候Features和Units有关,但更多的时候是无关的。
关于会议,有两条真理:
(1)会议是必需的;
(2)会议浪费了大量的时间。
受到邀请的会议没有必要全部参加。
如果会议让人厌烦,就离席。
为了合理使用与会者的时间,会议应当有清晰的议程,确定每个议题所花的时间,以及明确的目标。
到场的人依次回答以下3个问题:
(1)我昨天干了什么?
(2)我今天打算干什么?
(3)我遇到了什么问题?
站会对团队了解彼此的工作进度很有帮助,还能帮助自己整理思路。要是前一天的产出很低,会不好意思的。
迭代计划会议用来选择在下一轮迭代中实现的开发任务。在会议召开前必须完成两项任务:评估可选择任务的开发时间,确定这些任务的业务价值。
“凡是不能在5分钟内解决的争论,都不能靠辩说解决。”
唯一的出路是,用数据说话。
职业开发人员会学习安排时间,妥善使用自己的注意力点数。
肌肉注意力有助于改善心智注意力,而且不仅仅是简单的恢复。
关于注意力,我知道的另一重点是平衡输入与输出。
番茄工作法的真正好处在于,在25分钟的高效工作时间段里,你有底气拒绝任何干扰。
看来要好好实践一下番茄工作法。之前一直使用的是此刻,看来要继续下去。
优先级错乱——提高某个任务的优先级,之后就有借口推迟真正急迫的任务。
专业开发人员会评估每个任务的优先级,排除个人的喜好和需要,按照真实的紧急程度来执行任务。
在走入死胡同时可以迅速意识到,并有足够的勇气走回头路。这就是所谓的坑法则(The Rule of Holes):如果你掉进了坑里,别挖。
比死胡同更糟的是泥潭。
走回头路看起来代价很高,因为要把已有代码推翻重来,但是走回头路绝对是最简单的方法。
专业开发人员会用心管理自己的时间和注意力。他们知道优先级错乱的诱惑,他们也珍视自己的声誉,所以会抵制优先级错乱。他们永远有多种选择,永远敞开心扉听取其他解决方案,他们从来不会执拗于某个无法放弃的解决方案。他们也时刻警惕着正在显露的泥潭,一旦看清楚,就会避开。
业务方觉得预估就是承诺。开发方认为预估就是猜测。
承诺是必须做到的。
承诺是关于确定性的。
预估是一种猜测。
预估不是个定数,预估的结果是一种概率分布。
墨菲定律:如果可能出错,那么就一定会出错。所以对什么不要抱侥幸心理。
专业开发人员能够清楚区分预估和承诺。只有在确切知道可以完成的前提下,他们才会给出承诺。此外,他们也会小心避免给出暗示性的承诺。他们会尽可能清楚地说明预估的概率分布,这样主管就可以做出合适的计划。
计划评审技术(PERT,Program Evaluation and Review Technique)。
“德尔菲法”(wideband delphi)。
共识。
一组人集合起来,讨论某项任务,预估完成时间,然后重复“讨论-预估”的过程,直到意见统一。
1.亮手指
2.规划扑克
之前用斐波那契数列实践过,预估过卡片的点数。
- 3.关联预估
将卡片打乱铺开,按照任务的复杂度排序。最后,静默的排序终止。开始讨论取得共识。在实践的过程中,从有分歧到达成一致,还挺有意思的。
- 4.三元预估
控制错误的办法之一是使用大数定律。该定律的意思是:把大任务分成许多小任务,分开预估再加总,结果会比单独评估大任务要准确很多。
预估实践的比较少,个人觉得主要还是需要经验的积累。
即使有压力,专业开发人员也会冷静果断。
在压力下保持冷静的最好方式,便是规避会导致压力的处境。
应当避免对没有把握能够达成的最后期限作出承诺,这一点很重要。
快速前进确保最后期限的方法,便是保持整洁。
让系统、代码和设计尽可能整洁,就可以避免压力。
混乱会降低速度,导致工期延误,承诺失信。因此,要尽力保持输出成果整洁干净。
观察自己在危急时刻中的反应,就可以了解自己的信念。
选择那些你在危急时刻依然会遵循的纪律原则,并且在所有工作中都遵守这些纪律。
让你的团队和主管知道你正身陷困境之中。告诉他们你所制定的走出困境的最佳计划。请求他们的支援和指引。避免制造意外之外的诧异。
战胜压力煎熬的唯一方法,便是依靠那些你已经知道切实有效的东西——你平时遵守的纪律。
结对!
编程用的机器则整洁,行为也可预见。
“我的Mac就是我的女朋友。”
专业程序员的首要职责是满足雇主的需求。
深刻理解业务目标。
团队中每位成员都能签出任何模块的代码,做出任何他们认为合适的修改。
专业人士会结对工作。
因为至少对有些问题而言,结对是最有效的解决方法。
专业人士结对工作,因为这是分享知识的最好途径。
专业人士之所以结对,是因为结对是复查代码最好的方式。
有些时候,单独工作是正确的。
但是一般来说,和其他人紧密协作、在大部分时间段中结对工作,是最好的做法。
编程就意味着与人协作。
让一个程序员把一半的时间投入在项目A中,把其余时间投入在项目B中,这并不可行,尤其是当这两个项目的项目经理不同、业务分析师不同、程序员不同、测试人员不同时,更不可行。
这个团队应该配有程序员、测试人员和分析师,同时还要有一名项目经理。
其中有一名团队成员可能会拿出部分时间充任团队教练或Master的角色,负责确保项目进展,监督成员遵守纪律。
类似Scrum敏捷项目管理框架中的Scrum Master。Scrum敏捷实践有接触过,有时间这里的知识要再巩固一下。
专业的开发组织会把项目分配给已形成凝聚力的团队,而不会围绕着项目来组建团队。
每个团队都有自己的速度。团队的速度,即是指在一定时间段内团队能够完成的工作量。有些团队使用每周点数来衡量自己的速度,其中“点数”是一种关于复杂度的单位。
管理人员可以对分配给团队的项目设置一个目标值。
项目承包人的职责所在,便是清晰地定义和陈述项目的价值和意义,让项目得到公司管理层的认可和支持。
那些符合要求的毕业生有个共同点:他们几乎都在进入大学之前就已经自学编程,并且在大学里依然保持自学的习惯。
自学很重要,所以更要学会如何自学。
1.大师
2.熟练工
3.学徒/实习生
我们今天的做法和我所提倡的理想化的学徒制程序,这两者之间的主要差异在于技术方面的传授、培训、督导和检查。
成熟工匠是一名专业人士。
技艺是工匠所持的精神状态。
开源工具通常是最好的选择。
永远不要签入没有通过全部测试的代码。永远不要。
Git大法好!强烈建议未曾接触过Git的人开始使用Git对自己的软件版本进行管理。你会发现你打开了美好新世界的大门。
使用过一段时间的VIM,个人觉得不求精通,但求掌握,毕竟在一些时候,用它可以快速编辑一些代码。如果花时间精通它之后,你会发现它和IDE工具一样好用。
我现在用的是IntelliJ。你可以更方便地编辑代码,效率当然可以得到很大的提高。对于个人来说,如果不想购买License的话,有社区版可供使用。
有时候,最好用的问题跟踪系统可能是一打卡片和一个公告板。
在工作中使用的是LeanKit,个人使用更多的是Trello。创建多栏,在合适的时候把卡片从一栏移到下一栏。
我的持续构建哲学很简单:把它和源代码控制系统对接起来。不管什么时候,只要有人签入代码,就要能自动进行构建,并把结果状态报告给团队。
团队必须一直确保构建成功。
我目前接触过的是Bamboo。
不论选择什么样的单元测试工具,这些工具都要支持如下一些基本的特性。
(1)必须能够快速便捷的运行测试。
(2)对于测试是通过还是失败了,这些工具应该给出清楚的视觉提示。
(3)对于测试进度,这些工具也应该给出清楚的视觉提示。
(4)这些工具应该避免测试用例之间彼此通信。
(5)这些工具应该使编写测试变得十分容易。
现在我用的更多的是写Java时的JUnit。
这些工具用于在API层对组件进行测试。它们的任务是要确保组件行为是以业务人员和QA能够理解的语言来描述的。
我目前接触过的是cucumber,有时间需要好好实践一下。
程序员负责管理各种细节,这是我们的职责。我们通过管理各种最微小的细节来规范系统的行为。
整本书读下来,收获不小。之前很多在工作中接触过的方法或实践,并不是很清楚它们的理论或原理。读着读着很多时候发现,原来之前实践的方法叫这个名字。可以方便以后对实践进行更新或是更好的进一步思考。方法论的书,常读常新,把自己的实践和理论结合起来,防止走弯路的同时,也构建起了自己的理论体系。本书提到的很多实践,发现在ThoughtWorks的工作中都接触到了。感谢在TW的成长。
]]>自学就是完全自发、自主地进行学习。
自学和教学最大的不同,在于有没有终极目标。
之前自己很多时候不知道学那些知识有什么用,后来发现,过一段时间总会不经意间用到,功不唐捐。自学觉得就是让自己不会被落下,不断更新自己的知识。
对于自由的向往和追求,一直推动着我的学习。
除了知识带给人冲破蒙昧的自由感之外,还有自主学习带来的一切自己掌握的自由感。
而自学正是寻求人生目标和意义的最佳渠道。
这些都可以看作是对人生目标和意义的有益探索。这样的探索让人们不再臣服于自己的原始本能,享受到真正的快乐。
自己一晚上看资料学会视频剪辑,剪出来的片子能触动别人,这件事让我感受很深也很开心。
告诉我的,我会忘记;教给我的,我只能记住;让我参与,我才能学会。
本杰明•富兰克林
在我看来,这是因为根本就没有普遍适用的学习方法,只有最适合自己的方法。要找到适合自己的方法,就必须要对自己有深刻的了解。
在了解自己的基础上,要正视自己的不足。
也就是说,如果单纯靠传授,老师不可能教出比自己更优秀的学生。
迷信老师,或者完全拒绝老师,都不是理性的学习态度。
很多时候,自己解决不了的问题,请教人,会因为三两句话瞬间得到解决。
达•芬奇 本杰明•富兰克林 托马斯•爱迪生 安藤忠雄 李宗盛,罗大佑,陈升 韩寒,东野圭吾
学习是没有止境的,但是学校生活总会结束。
之前总会遇见有人说,等毕业了就不用学了,真是太天真和无知了。
作为一个现代人,我们有各种各样的科技手段来帮助我们学习,弥补老师的缺失,或者取代老师的部分职能,让我们更容易形成适合自己的学习方法。
在我看来,虽然教学看上去像是在传输知识和经验,但究其本质,乃是人为地引导和辅助自学。
一般来说,计划包括三个元素:起点,路线图和目标。
学习是一个动态的过程,是一个不断反思和调整的过程。目标,步骤,都不是固定的,而是不断演进的。
心流指的是人们全情投入一件事情,注意力极度集中,并产生持续愉悦感的心理状态。
要进入心流状态,人就必须满足以下七个条件:
(1)知道要做什么事
(2)知道怎么来做
(3)知道自己的进度
(4)知道下一步该怎么走
(5)认为事情的挑战很大
(6)相信自己的能力足够
(7)不被干扰的自由
对于自学者来说,第一条是目标,第二条是方法,第三条是监控进度,第四条是制订和跟随计划,五、六条是自己的心理,第七条则是学习的环境。
好的进度指标应该是量化的。
把自己的学习过程可视化,展示出来,让自己能随时看到自己的整体进度和完成情况。
觉得可视化能更好的查看进度的实施。这和网上支付更容易消费更多很像。
第一,你得能客观地判断和看待学习的情况。
我们要尽量把学习的情况纪录下来,然后客观地来看。
第二,你必须要有一个判断标准。
除了不断尝试之外,还有一个较为容易入手的办法,那就是把标准量化。
第三,反馈必须是可执行的。
没有具体指出问题的点,也没有给出具体的改进措施。
在问别人要feedback时最容易遇到这一点,所以尽可能让对方给出详细的反馈。
心得技巧分为两种,一种是知识和经验,另一种则主要针对学习方法。
记录下你的心得技巧
自学者可以试着组成学习小组,相互示范自己的心得。如有需要,也可以找一位老师进行示范。
有时候示范会一点就通。
老师的所有能力中,有一项是无可替代的,那就是启蒙,或者理念的传输。
“师者,所以传道受业解惑也。”
评判老师的好坏,在大多数情况下是在评判这位老师适不适合自己。
优秀的老师应该是教育的归教育,利益的归利益。
现在很多时候是把教育更多地当作挣钱的一种手段。
优秀的老师应该能全面了解学生的背景以及学生对于学习的诉求,并不断调整自己的教学方式,以更好地达到教学效果。
优秀的老师并不会以权威来压制学生,会允许学生提出自己的思考,并会很愿意跟学生进行讨论。
在做任何事情之前,都应该明确目标是什么。
人在主动想要学习一个东西的时候,基本上都会有一个长期目标。虽然这个目标不一定清晰或者现实,但毕竟有一个。为了更好地确定学习的方向和路线,我们需要把这个长期目标尽量地细化。
在这个阶段,只需有一个大体的感觉即可,不必过于详细,也不用怕出错。
在海量的材料中探寻、收集、整理并吸收是一件很美好的事情,也是学习的一部分,所以,纵然很花时间,还请大家一定怀着积极的心态来做这个事情。
搜索能力也是个人素质的一点。
从最简单,最直观的东西做起,不管做的再差,你总有可以自我反馈的基础。
坏的实现总好过好的想法。
这样能迅速让人尝到的“甜头”(或者我们叫它quick-win)可以帮助我们在进一步深入学习前建立起信息,而信心可以帮助我们在长时间的学习过程中始终保持动力。
深入的学习必然会有一些看似枯燥的练习,也需要强大的毅力来坚持。这些必要的刻苦过程,任何方法论都无法代替。
一定要坚持。而且正负反馈要平衡。
阅读大量的书籍可以让各种知识体系形成交叉,更容易触类旁通。阅读量大到一定程度,就容易促成质变,让人的整体思维模式和学习方法提升一个台阶。
读太多书反而变蠢的原因有两个:无差别阅读导致的浪费时间,以及尽信书导致没有自己的思考。
《如何阅读一本书》讲述了分层阅读法。把一本书分成骨头、血肉和灵魂三个层次。
- 骨头就是它的整体目录,读者通过它可以快速浏览,搞明白这本书到底写的什么,然后用一句话把它总结出来,这是检视阅读。
- 血肉就是把整本书的逻辑结构和论点、论据有机地组织和支撑起来,形成一个完整地架构,这是分析阅读。
- 灵魂则是这本书的主题,读者通过把多个同样主题地书结合起来阅读,以形成一套兼容并包的知识体系,就是主题阅读。
对于提供新信息的书,采用从骨到肉地读法;对于确认已知信息的书,找出不同地论点;对于不同观点的书,集中对比阅读。
随时保持批判性思维,并在阅读不同书籍的时候使用不同的策略,可以让阅读更加高效,更有价值。
在阅读文章的时候,最好抱着明确的目的来阅读,通过搜索引擎来搜索和寻找。
Coursera,iversity,Udemy,KhanAcademy,公开课,TED
游戏式的工具比较适合入门,但在一段时间之后,还是需要回归更加严肃一些的学习方式。
第一个问题是:读者是谁?
第二个问题是:目标是什么?
学习资源的局限性就在此,它提供的是基本的知识,是看得见的东西。要学到东西,还有很多隐式知识,需要时间的积累和自己深度的思考。
我的学习计划包括一个长期目标、一个中期目标,以及一系列短期目标。
长期目标应该是半年到一年期的目标。长期目标应该比较具体。需要明确的是,我们不是在制定详尽的学习计划,我们只是在我们的学习生涯中标注出一个个的参照点。
中期目标,或者阶段目标,是对长期目标的切割。我一般把中期目标设定为一个月。
有了中期目标,实施一段时间之后,我们就能对长期目标的可行性作出判断。
短期目标就是每天都想要达到的目标,所谓的“速效目标”。我们应该尽量把它量化。短期目标的目的是让我们坚持。
之前制定长期计划时容易比较抽象,而且做一做的容易变成一个很详尽的计划,导致可实施性降低。
精益生产法(或者叫丰田生产法)。以结构化的形式公开展示信息以帮助某种流程的方法,看板。
第一,你学得快不快乐;第二,你学得快不快。
自己有时学习方法调整的不是很好,需要改进。
全力以赴,穷则思变。
学习就是用重复练习来找到方法,形成习惯。薄弱点和比较好的部分混在一起,就是在浪费时间,因为薄弱点的重复变少了,而比较好的部分其实又不必重复。
注重整体效果,而不要被细节所困扰,打断自己的心流,浪费时间。
想要保持良好的练习效应,我们就必须然大脑和肌肉充分休息。
任何需要思考的练习,都可以使用脑内练习的方式。
知识体系的形成靠的是把知识外部化。
公开演讲。
工作坊。
写作。
写作驱动开发。
两种很有趣的大脑锻炼法,一种锻炼我们的初心,一种锻炼我们的观察能力。
人喜欢拖延工作的原因常常是:一、工作不够细分,过程展示不够清晰;二、截至日期还早。
对于自学者来说,
第一,是对要学的东西没有爱。
第二,是没有紧迫感。
第三,是不能坚持。
第四,是无法开始。
第五,是完美主义。这里指的完美主义者有三个特点,第一是准备工作一定要做足,第二是方法方式一定要用对,第三是一犯错误就沮丧。
第六,是并行失误太多。
最后,是时机未到。
第一重意义就是我们所知道的,特别适合自己的学习方法或者技巧。第二重意义则是通过长时间练习而养成的习惯和娴熟。
在设定目标和期望值的时候,一定要恰当。
推动力有其特定的适用场景和作用,不过,驱动的力量才是长效的力量。
第一,不要同时做太多的事情;第二,把所有信息整理起来,并且形成整理的习惯。
一件一件的把事情做好。
一个真正优秀的自学者是不惧怕学校摧残的。
原来经常听到这句话,带着镣铐跳舞也要跳的好看。
总的来说,自己了解到不少方法,也找到一些自己的不好的实践,还是书里的那句,结合自身的情况发展出适合自己的自学方法。
自学大法好!
我们要自学 - 张玳
]]>下列值是falsy:
false
0
""
null
undefined
NaN
其他所有的值都是truthy,包括"0"
,"false"
,空的function,空的array,空的object。
比较运算符:==
判断value是否相等,忽略type。
比较运算符:===
判断value和type是否相等。如果可以的话尽可能使用该运算符,使用==
可能会导致某些逻辑错误。
false,0和“"(空string)在比较时被当作相等的。因此尽可能使用===
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
null和undefined在比较时被当作相等,和其他值不相等。如果一个变量没有被声明或被定义(例如一个argument被指向一个不会返回任何值的function,一个对象没有被分配值),那么这个变量会被赋予一个特殊值:undefined。因此尽可能使用===
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
NaN是一个特殊的数字值,意思是Not-a-Number,是算数运算产生invalid结果导致的。和任何值都不相等。尽可能使用isNaN()判断是否为NaN。
1 2 3 4 5 6 7 8 9 10 |
|
注意:NaN不等于它自己!
1 2 3 4 |
|
啊,吃我一记大坑 知道这些陷阱可以有效提高工作效率。
参考文献
]]>自己14年才开始使用Git,之前就是在本地很low地创建不同的文件夹来保存代码。使用Git后,“Diff,Add我闭着眼,reset回退你沉醉了没”可以很自由的对代码进行版本管理,而不用担心自己会丢失历史版本。
Git中使用git log
查看之前提交的commit日志。
一般来说,在repo中输入git log
后:
1
|
|
默认是这样的,会新打开一个page显示如下的信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
git log
会按提交时间列出所有的commits,最近的commit排在最上面。每次的commit都有一个SHA-1校验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示commit说明。信息很详细,但是在使用时显得很多,容易分散注意力。git log
有很多选项可以帮助我们更好地工作,进行一定的配置后更加方便我们啪啪啪写代码。
经常使用的是format
,可以定制要显示的记录格式,这样的输出便于后期编程提取分析,例如:
1
|
|
1 2 3 4 5 |
|
下面列出了常用的格式占位符写法及其代表的意义:
选项 | :说明 |
---|---|
%H |
:提交对象(commit)的完整哈希字串 |
%h |
:提交对象(commit)的简短哈希字串 |
%T |
:树对象(tree)的完整哈希字串 |
%t |
:树对象(tree)的简短哈希字串 |
%P |
:父对象(parent)的完整哈希字串 |
%p |
:父对象(parent)的简短哈希字串 |
%an |
:作者(author)的名字 |
%ae |
:作者的电子邮件地址 |
%ad |
:作者修订日期 |
%ar |
:作者修订日期,按多久以前的方式显示 |
%cn |
:提交者(committer)的名字 |
%ce |
:提交者的电子邮件地址 |
%cd |
:提交日期 |
%cr |
:提交日期,按多久以前的方式显示 |
%d |
:对应的branch分支名 |
%s |
:提交说明 |
使用format
时结合--graph
选项,可以看到开头多出一些ASCII字符串表示的简单图形,形象地展示了每次提交所在的分支及其分化衍合情况:
1
|
|
1 2 3 4 5 |
|
下面列出了一些常用的选项及其释义:
选项 | :说明 |
---|---|
-p |
:按补丁格式显示每个更新之间的差异 |
--word-diff |
:按word diff格式显示差异 |
--stat |
:显示每次更新的文件修改统计信息 |
--shortstat |
:只显示–stat中最后的行数修改添加移除统计 |
--name-only |
:仅在提交信息后显示已修改的文件清单 |
--name-status |
:显示新增、修改、删除的文件清单 |
--abbrev-commit |
:仅显示SHA-1的前几个字符,而非所有的40个字符 |
--relative-date |
:使用较短的相对时间显示(比如,“2 weeks ago”) |
--graph |
:显示ASCII图形表示的分支合并历史 |
--pretty |
:使用其他格式显示历史提交信息,可用的选项包括oneline,short,full,fuller和format(后跟指定格式) |
更多的资料可以在git-log中查看。
如果每次在查看commit日志时都需要加上这些选项,会很麻烦。我们可以设置一个简单的alias。一般来说,上面的命令命名为git lg
,当然叫什么取决个人喜好。在设置好后,输入git lg
就会看到和之前一致的输出了。
1 2 |
|
1 2 3 4 5 |
|
我们可以看到每次的输出会在一个新的page显示,需要输入q
才能退出。我们可以在设置alias时增加一条--no-pager
的选项,让输出和输入的命令在同一page下显示。此时,可能commit的日志很多,可以增加一条-6
的选项将显示的日志数量限制在6。
1 2 3 4 5 6 7 8 9 |
|
此外,你可以对输出结果的颜色进行设置,使输出结果更直观。毕竟工具就是为了让人可以将注意力更多关注到有产出的工作上,而不被其他东西分散。
参考文献
]]>正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友。使用简单的命令就可以在不同的Java版本之间进行切换。如果你使用过rbenv,你会发现jEnv就如同rbenv的Java版一样。
基本使用:
在Mac OS X下使用Homebrew安装jEnv:
1
|
|
安装成功后需要进行一下简单的配置,让它可以起作用:
使用Bash的情况
1 2 |
|
使用Zsh的情况
1 2 |
|
好了,jEnv已经安装好了,让我们来看一下它找见哪个Java版本了:
1 2 |
|
它只找到了系统默认的Java,即使我已经下载了其他版本的Java。*
表示当前选择的版本。
和rbenv不同的是,jEnv不能自己安装任何版本的Java,所以需要我们手动安装好之后再用jEnv指向它们。
安装Java 6,需要在Apple进行下载。它将安装到/System/Library/Java/JavaVirtualMachines/
下;
安装Java 7,可以在Oracle进行下载.它将安装到/Library/Java/JavaVirtualMachines/
下;
安装Java 8,可以在Oracle进行下载.它将安装到/Library/Java/JavaVirtualMachines/
下。
使用jenv add
将Java 6加入jenv中:
1 2 3 4 |
|
运行jenv versions
时会显示:
1 2 3 4 5 |
|
同样的,使用jenv add
将Java 7加入jenv中:
1 2 3 4 |
|
1 2 3 4 |
|
现在运行jenv versions
会显示:
1 2 3 4 5 6 7 8 9 10 11 |
|
对于博主这种不是处女座的人来说,也觉得需要对版本再管理一下,使用jenv remove
可以从jEnv中去掉不需要的Java版本:
1 2 |
|
整理后,再运行jenv versions
会显示:
1 2 3 4 5 |
|
选择一个Java版本,运行jenv local
,例如:
1 2 3 4 5 |
|
DangDangDangDang,我们已经成功地指定了某文件夹中local的Java版本。
你可以运行jenv global
设置一个默认的Java版本,运行jenv which java
显示可执行的Java的完整路径。
你也可以在特定的文件夹下使用.java-version文件来设定Java的版本。当我需要在Project中使用Java 6时,我仅仅需要把1.6.0.65
作为内容保存在.java-version文件中,当我进入该文件夹时jEnv会自动地帮助我设定local的Java的版本。
没错,我们现在有了Java的多个版本,并且可以在它们之间轻松切换。更多的使用方法可以在jEnv官网的官网查询到。
参考文献:
]]>