第一次听说这个比赛还是去年在知乎上,当时也不知道这个比赛具体是怎么回事,当时自己还是一个小白,忙着搞懂各种主流的机器学习模型算法。

当时在我心中,模型算法是数据挖掘的最重要的组成部分,搞懂这些才能真正搞定数据挖掘。我当时对算法模型和数据的理解是:模型就是风车,数据就是流水。我要做的事就是撘一个强健的风车,让数据流过。

当我还没接触实际的工作前,我还没有没有从编程工转向挖掘工。我太注重编程本身了,而忘记我自己真正要挖掘的宝藏。

我以前在Quora上搜如何成为数据科学家,我发现很多有经验的数据科学家他们都把“对数据的敏感和兴趣”作为数据科学家最重要的特征,而“了解各种算法模型并能应用到数据上”才是第二重要的。我当时不是太理解,我觉得后者才是更重要的。

参赛感想

这次参赛算是我学习数据挖掘第一次实际的挖掘,以前学习各种算法模型都是准备的很好的数据,只要套上算法模型就能跑的很好。所以我一开始就拼命的去找类似的大赛,看看获胜者他们用的模型是什么。

这几天我好像抱着一堆瓶子,拼命的想把巨大的石头(数据)塞进瓶口里,看起来工作量很大,流了很多汗,其实什么都没有干。今天在看一个类似的比赛选手答辩的时候的视频,突然明白自己好像走了一个死胡同。自己拼命的想这找一个合适的瓶子(模型),其实我更应该做的是把石头(数据)磨碎。

模型本身不重要,他只是一个载体,更重要的是数据。

第一次参加这样大型比赛,有点激动也有点惶恐,如何将所学的应用到实际,还有在实际中提高自己还有待自己“挖掘”。虽然这个比赛奖金“丰富”,但是我觉得在这个比赛中得到的体会乐趣比奖金更诱人。


比赛还有一个月,在这里立个小目标,争取跑到到前五页,我也会尽量抽时间把自己感想写出来。
未完待续。

这是我从Quora上看到的一篇非常简短但详细的数据科学家的‘技能点’
来自eBay的一个数据科学家的回答
翻译来自Quora回答

这是面试谷歌、英特尔、脸书等大的世界五百强公司的数据科学家相关岗位常见的技术要求,在我看来主要有七点

  • 基本的编程基础

你应该了解一门统计学相关的编程语言,比如说RPython(同时要了解NumpyPandas库),还要一门数据库查询语言比如SQL

  • 统计学

你应该要能解释零假设、P值、最大似然估计和置信空间这些短语,统计学在非常巨大的数据库里压缩数据和从挑选最重要的特征非常重要,在你得出结论和设计实验过程中也帮助巨大

  • 机器学习

你必须能够搞懂K-近邻、随机森林和集合方法等机器学习算法,这些算法基本上都在RPython中得到实现,这些算法能告诉你雇主你能够将计算机科学运用在实际的管理中。

  • 数据重组

你应该要能够“清理”数据。比如数据库中”California” (加利福利亚)和“CA”是一样的,数据库里面可能出现用负值代表人口。这个总的来说就是识别坏(或者不正确)的数据然后校正(或删除)他们。

  • 数据可视化

数据科学家不能就只是自己搞懂就行,他们需要把他们发现告诉你的产品经理,这样就能确保数据能很好的应用到程序里面去。所以,熟悉数据可视化工具比如说ggplot非常重要(这样你就能展示你的数据而不是仅仅谈谈而已)

  • 软件工程

你应该了解算法和数据结构,因为这些东西在你写高效率的机器学习算法时非常重要,知道如何使用分支和使用高效的数据结构:队列、数组、列表、堆栈、树等等。

  • 产品管理

这个绝对是有争议的,但是那些了解产品的人将会知道什么指标是最重要的。这里有很多数据可以用来做A/B测试,但是产品导向的数据科学家将会把最好的指标用来做测试。你要知道这些的意思:可用性测试、线框、保留和转换率、流量分析、客户反馈、内部日志、A/B测试。

引言

递归是高度抽象化问题的一个好东西,我们能从很多算法书里面看到这个,
但是递归虽然对于人来说好理解,但是计算机执行起来会损失性能,一个差的递归可能会耗光计算机的资源

接下来我们来看一个非常经典的算法问题Fibonacci数

f(n) = n (n < 2)
f(n) = f(n-1) + f(n-2)   (n >= 2)

我们可以很轻松的用递归解决掉它

def fibonacci(n):
  if n < 2:
      return n
  else:
      return fibonacci(n-1) + fibonacci(n-2)

n比较小的时候很快就出结果了,但是当n大于100时候要很久才能出结果,如果n大于1000,直接报出超出迭代深度的错误(python默认迭代深度是1000)

现在我们来解决两个问题

  1. 为什么n大于100时候就很久才能算出结果
  2. 为什么n大于1000就报迭代深度的错误

首先我们要知道一个概念就是堆栈段,每个进程开始运行时都会初始化一个堆栈段,这在物理上就是一小块内存,初始化堆栈段的时候计算机要做一些看起来同程序毫无关系的事情,比如说将寄存器的值推入堆栈里面等等

当你在运行主程序的时候你调用一个子函数,系统又会在当前堆栈段新建一个堆栈段,你子程序运行完了后会删掉这个堆栈段回到主程序,但是递归有个问题,就是他调用子程序的时候不会立即返回又会再调用自己

没办法因为子程序还没返回,所以计算机又初始化一个堆栈段,一个n为10的fibonacci函数就会初始化掉 2 ** 10 = 1024个堆栈段,n越大值会指数型增长,虽然1000个初始化在当今计算机上发不了多少时间,但是当我们n大于20就要 百万次初始化了

这就是为什么n很大的时候要很久才能算出结果,在一些单片机上面,循环调用空函数就是延时的功能,原理也就是堆栈初始化耗时间,而且不但耗时间假如像递归这样调用上百万次初始化而不返回将会耗掉大量内存在堆栈段上。

对策

要解决这两个问题,一种方法是改算法,使用非递归算法,这个网上有很多,感兴趣的可以去搜一下,第二种是使用协程解决递归问题

如何使用协程来解决递归呢我们先改主程序,将return换成yield

def _fibonacci(n):
  if n < 2:
      yield n
  else:
      yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

接下啦我们运行一下函数

>>> _fibonacci(10)
 <generator object _fibonacci at 0x00000013A74779E8>

没有返回结果,返回一个生成器,那我们用list简单的试一下吧

>>> list(_)
......
......
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

生成器小知识

这里补充几点生成器的知识,懂得可以跳过

生成器大家都用过,无论是Python2Python3都不陌生,最简单的生成器是这种

>>> items = ( x for x in range(10))

我们一般搭配for来使用

>>> for i in items:
...         print(i)
...

我们也可以用协程来实现这个生成器

def iter_func(n):
    for i in range(n):
        yield n

像上面一样使用for就能实现一样的功能,在这个例子里面yield好像变成了一个return的作用,在for语句中,随着每次请求都会return一个数过来

在这个里面yield好像就是这么个功能,但是yield的作用远远不止于此

我们现在来改一下这个函数

def iter_func(n):
    for i in range(n):
        r = yield i
        print('result', r)

我们用list来运行一下这个函数

>>> list(iter_fun(2))
0
result None
1
result None

r返回了一个None,我们尝试自己实现一下for循环,有两种方式

  • next(generator)
  • generator.send(msg)

先尝试用next

>>> it = iter_fun(2)
>>> next(it)
0
>>> next(it)
result None
1

我们介绍一下next函数, next接受两个参数,第一个是生成器,第二个是返回的默认值,next函数在这里相当于下面这个函数

def next(iterator, default=None):
    try:
        iterator.send(None)
    except StopIteration:
        if default:
            return default
        else:
            raise StopIteration()

为什么第二个执行了print函数而第一个没有执行?

生成器工作原理

这里我们介绍一下生成器的工作原理

当我们使用调用一个函数的时候,一般是碰到return或者执行全部函数就会返回父函数

但是生成器不同,假如他执行函数碰到yield,他就会直接返回一个生成器。

这个生成器我们可以把它看做是邮递员,我们必须写好目的地,他才会帮我们把信寄出去。

现在我们分析一下生成器的具体流程,我们先定义一个简单的生成器

def mygenerator(n):
    while True:
        r = yield n
        n -= 1
        print('result', r)

然后我们调用这个生成器

>>> i = mygenerator(10)
>>> i
<generator object mygenerator at 0x7f420a339d00>

我们得到一个生成器,我们先尝试发送一个地址给“邮递员”

>>> i.send(0)
...
TypeError: can't send non-None value to a just-started generator

我们得到一个错误,必须传递一个None,我们先不管,先送一个None值过去

>>> i.send(None)
10

我们得到一个10,再送一个地址过去

>>> i.send(None)
result None
9

我们现在来分析一下代码,第一次调用的时候直接返回了,第二次调用我们从r = yield n那行开始执行,并且运行到第二个r = yield n那里停止了

就可以解释上面为什么要第一次传递None过去,因为第一次调用它会直接返回yield后面的值给我们,第二次调用 我们可以根据第一次生成器递给我们的值,决定我们第二次想寄的“信”,因为第一次传递过去“信”并不能被处理,所以Python强制我们传递一个None值过去


我们回到上面的函数

def _fibonacci(n):
    if n < 2:
        yield n
    else:
        yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

我们来分析一下流程,为了解决上面的问题我们先把函数简化,去掉递归

def f(n):
    yield (yield n) + (yield n - 1)

我们先创建一个生成器i

>>> i = f(5)
>>> i
 <generator object f at 0x7f4a421d8f10>

我们先启动i

>>> i.send(None)
5

我们再把得到5传给i

>>> i.send(5)
4

我们得到yield n -1返回的4,我们再把4传给i,得到最终结果

>>> i.send(5)
9

假如我们把后面两个send的值换成其他值我们会得到不同的结果,这里我们可以看到我们,要实现上面函数必须要依靠一个,保存我们返回的生成器,然后依次调用生成器返回结果,具体代码如下

def fibonacci(n):
    stack = [ _fibonacci(n)]
    last_result = None
    while stack:
        last = stack[-1]
        try:
            if isinstance(last, types.GeneratorType):
                stack.append(last.send(last_result))
                last_result = None
            else:
                last_result = stack.pop()
         except StopIteration:
             stack.pop()
     return result

我们这里用stack作为我们的堆栈,用last_result保存上一个生成器返回的值

总结

我们使用协程解决掉了递归错误,但是这个方法并不可以给我们算法加速,虽然n为1000以上不会报递归错误,但是等待的时间还是很长很长。。。

虽然协程在这个方法里面并没有起到多大作业,协程在算法方面还是没有太多帮助,协程在计算机I/O还有网络请求方面有更好的效率,但是这次尝试让我们对协程如何使用有了一个清晰的了解

有兴趣的可以去了解一下协程在异步网络请求的应用

前言

因为手头自己有三个服务器,所以想折腾一下负载均衡。

两个CentOS,一个Ubuntu,都是比较新的。

一开始准备用haproxy来做负载均衡服务器,因为haproxy相比与nginxcookiesession支持比较好,但是由于两个原因还是放弃了。

  1. 服务器被阿里云封掉

简单的在haproxy中设置后端服务器后,过一段时间就显示强制备案页面,由于我的域名没有备案。

后来我翻看了nginx日志发现,haproxy默认在request header里面带了X-Host,被阿里云发现了,这里提供一个解决方法

# 删除掉你header里面的 Host
# 在backend里面添加一句
http-request del-header Host

然而nginx里面默认是没有添加Host这个的,要你在localtion中添加两句,如下面

    server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            www.example.com; # add Host
        proxy_set_header X-Forwarded-For $remote_addr; # add X-Forwarded
  }
}
  1. haproxy支持多开

我试了很多种选项,确定pidfile、改变uidgid等等,haproxy似乎可以允许很多个相同进程绑定同一个端口,虽然可以通过pid来写一套类似service管理的脚本,但终归很麻烦

我看网上有人写了这个脚本,但是nginx自带了,还是用nginx比较好,而且ansibleservice的交互还不错。

nginx负载均衡

nginx负载均衡是通过反向代理来实现的,也就是把一台服务器的压力分摊到多台上面

要想实现这个必须要有后端服务器,假设我们有一台后端服务器1.1.1.1,在代理主机的nginx配置系统location里面只要添加一条proxy_pass就行了

    server {
listen 80;
server_name example.com;
location / {
    proxy_pass http://1.1.1.1;
}
}

上面只是简单的实现了一个反向代理的功能,当你有一个后端服务群的时候,你就要使用负载均衡模块了,负载均衡模块在nginx配置特别简单,添加一个upstream模块,把服务器ip或者域名放到里面

  upstream webservers{
    server 1.1.1.1 weight=10;
    server my.domain.com  weight=10;

}

然后修改proxy_pass后的为http://webservers就行了

ps: nginx对于后端反向代理服务器有个max_failsfail_timeout属性,你要是设定了一个max_fails次数,你代理服务器拿取失败了几次就会在fail_timeout值之后尝试,和haproxyretry属性差不多,但是似乎haproxyretry不好使,我故意使用两个错误ip和正确ip,结果nginx能一直正确返回正确ip响应,而haproxy有时候能,有时候不行。

nginx错误日志

在调试nginx碰到一些错误,记录一下如何系统的解决方法

  • 调用service nginx start失败

首先看给的错误信息,假如让你看systemctl status nginx.servicejournalctl -xn,输入去看

  1. 格式错误(format error)

一般你写的nginx的配置文件有问题,这时候可以用nginx -t检查格式,修改正确后会显示success

  1. 无法绑定地址(bind error)

一般是因为有别的应用程序占用端口造成的,这时候用netstat -tulpn检查端口,然后选择kill掉占用端口的程序或者换一个端口

ansible playbook 编写

具体代码可以参考nginx均衡负载ansible-playbook
首先你得写一个hosts

[ali]
my ansible_ssh_host=1.1.1.1 ansible_ssh_user=root 
[tencent]
main ansible_ssh_host=1.1.1.2 ansible_ssh_user=root
[digital]
google ansible_ssh_host=1.1.1.3 ansible_ssh_user=root

前面[ ]包着的是组名,最前面的mymaingoogle别名,后面就是ip和用户名了。

写完hosts后要写两个nginx配置文件一个代理服务器的配置文件和一个后端服务器配置文件,playbook很简单就是复制nginx配置文件和重启nginx

---
- hosts: tencent
  remote_user: root

    tasks:
  - name: copy nginx config file 
    template: src=~/test/lunge_proxy.conf  dest=/etc/nginx/conf.d/lungelog.conf
    notify: restart nginx

    handlers:
  - name: restart nginx
    service: name=nginx state=restarted enabled=yes

解释一下notify,在复制完成之后就启用一个handler完成nginx的重启,当然这里也可以使用reload,假如在生产环境的话。

客户端和代理的playbook差不多就不多介绍了。

引用

nginx的配置、虚拟主机、负载均衡和反向代理

驱动力是什么

驱动力就鞭子,小的时候我们被父母教育考的好就是棒棒糖,考的差就是鞭子,等我们走进社会,工资就是我们是驱动力。

我们看到过那些年薪百万的程序员,也看到过一些碌碌无为的码农,每个人都想成为那群大牛,工作得心应手、万人敬仰,工资难以”望其项背”.

但是我们同大牛和码农(差点打成马蓉…..)有什么区别呢,有些人说是人家那些大牛早早就积累了十万个小时,我们同大牛只是差了十万个小时.

这从某一方面上来看是对的,从某一方面来说又是不对的,君不看那些在公司辛辛苦苦工作几十年的码农早就积累了几十万小时,但是他们依旧是码农,除了业务逻辑比新手强。

那是什么原因让几十万个小时造不了一个大神呢?

很简单,就是驱动力

码农是以工资作为驱动力的,而大神是以兴趣为驱动力的,很多码农一开始都同大神一样被编程的乐趣而吸引,然而大神坚持下来了,而码农呢,慢慢的像小孩子玩厌了新玩具,在慢慢的走入社会被工资左右,在慢慢的就开始盼望早点下班……

怎么改变

环境对我们的影响是潜移默化的,我们处在这个环境里面可能不知不觉就慢慢改变我们自己了

那我们如何改变自己

工作环境

假如你是一个工作党,找一个好的开发团队对你的影响是巨大的,假如你的小组死气沉沉,最好换一个即使工资很低

假如你是一个学生党,比如我,尽量参加学生社团,那种偏技术的部门,在同一个部门里面一起奋斗的感觉非常好。

学习环境

  • 在搜索引擎上面多走几步

很多时候我们遇到问题,google一下解决了就完了,我们要多问几个自己几个问题,这个问题为什么产生,如何避免,下一次还会遇到吗,还有及时收集自己的问题多总结,你要知道圣斗士之所以那么牛是因为人家从不在跌倒的地方跌倒第二次,你要知道bug不是我们的试卷,bug是我们的成神的补丁

  • 培养开源精神

github没事都上去溜达溜达,看到好的项目可以跟进,看看人家的代码同你的有什么不同,开源不代表抄袭,任何创新都是从模仿开始,不要老想着搞个大新闻大项目,其实很多项目都是从小项目开始的

  • 多输出

其实很多人不知道,写东西也是学习的一种方法,因为很多时候我们学的东西有时候学的模拟两可,写出来有助你理清脉络,而且帮助后来者少走弯路,何乐而不为呢。

有些人说我没什么想写的,你找一些外国博客翻译也是可以的,通过翻译学习,一方面锻炼自己,一方面让更多人了解国外文化。

总结

成神是很难的,但是只要你在路上,不要回头就不难了。

看过好几把关于机器学习的书,但是很多书只是停留于算法原理阶段,或者更着重介绍算法原理, <<集体智慧编程>>这本书更多的是从实践来介绍书,比如你要撘一个推荐系统你怎么做,,还有怎么来做一个垃圾邮件过滤系统等….
接下来介绍一下我对于做一个推荐系统的理解.

前言

推荐系统是一个什么东西呢
简单来说就是两个字—-推荐

推荐系统的出现是伴随评价系统出现而出现的,评价系统就是我们的打分制,比如对一部电影每个人都对他进行打分

用户 电影A评价 电影B评价 新电影C评价 新电影D评价
张三 5.0 3.5 4.9 2.0
李四 2.5 5.0 2.0 3.0
王二 3.1 4.7

比如现在三个人张三李四王二,王二有两部电影没有看过,但是张三和李四看过了,现在就是要把电影C或D推荐给王二,推荐系统就是从其他数据分析来确定推荐次序

相似度

推荐系统如何根据其他用户的数据来推荐呢

这里就要介绍一个相似度的概念,也可以加权系数,对于张三和李四,这两个人都看过电影C和D,但是我们该听谁的呢,我们可以把两个人的评价加起来然后排序,但是假如张三和李四品味相差太多,而且张三的评价可能会把某个王二喜欢看的电影评价总值拉低

这个时候我们就要考虑一个相似度问题,就是尽量避免一些品味同王二不同的人对排名造成影响

这里我们引进相似度这个概念,我们看张三和李四对电影A、B同王二的差别,这里我们使用距离这个概念,我们使用距离公式 l=((r1^2 + r2^2+....rN^2)^1/n)

然后通过距离算得S(相似度)=1/(1+l)

当l=0时,两者相似度为1(最大),当l很大时,相似度几乎为零

用户 电影A评价 电影B评价 相似度
张三 5.0 3.5 0.23
李四 2.5 5.0 0.59

通过求相似度(权重)我们就把同我们臭味相投的人的评价提高了,那些同我们不一样品味的人评价作用降低了

选择

基于人物的评价推荐有个缺点就是人的评价有时候会比物品多,而且变化频繁,假如使用上面的方法的话,每次每一个用户评价过后,就要重新算一次相似度了,假如用户不多或者电影不多还好,一旦数据偏多服务器就扛不住了

所以我们要换一个角度出发,计算物品的相似度,这样图表就变成这样了

电影 用户-张三 用户-李四 相似度
A 5.0 2.5
B 3.5 5.0

这样有个好处就是可以离线处理数据,我们可以搭建一个离线处理系统,让一个系统专门处理数据,当服务器需要的时候再拿取过去.

总结

搭建一个简单推荐系统并不会你想象中那么简单,但是要搭建一个功能强大速度快的推荐系统需要的不仅仅是这么一点,还需要考虑系统的稳定和速度.

git是当今流行的版本控制工具,一般我们可能只要会push, pull就可以了,
但是当我们同别人共同工作的时候,我们必须要了解git协同开发的一些重要流程.

前言

git作为当今最流行的版本控制工具之一,当时开发出来就是为了管理Linux庞大源代码的分布式版本控制工具.
由于Linux源代码过于巨大,仅靠一个人的力量是完成不了的,那就必须把工作分配下去,然后将代码合并,所以git一开始设计的时候就是一种分布式的、多分支的

概念

所以git最重要的就是分支这个性质,分支是什么呢.

要了解分支必须要了解git工作原理.

git工作原理很简单就是addcommitaddcommit….,简单来说就是添加记录,添加记录,保存快照,添加记录,添加记录,保存快照

git工作分支流程-来源网上

如上图,随着master分支快照的一个一个建立,软件就慢慢的迭代下去了

分支工作流程

接下来我们要着重讲一下分支,我们看到master分支的v0.1版本,我们已经开发出稳定的v0.1版,这时候我们决定开发一个新功能.

在这里我们分了一个Develop分支,我们在Develop分支开发新代码.

这时候我们发现v0,1的一个bug,假如是没有使用版本控制的话,一般人会停下手中的活,然后从当前的新代码处来修复这个bug,当这个bug很简单的时候,我们不会遇到很大困难,但是当bug藏的很深,而且新代码隐藏了这个bug,或者被这个bug影响,这时修复工作就变得很困难.

还好我们有git,我们从v0.1直接分一个Hotfix分支,这两个分支的父都是v0.1,我们直接从稳定版本修复,不牵涉到新代码,这样修改好后我们就能很快从Develop分支继续工作了

而且这样有一个好处我们将masterDevelop分支合并的时候很大可能不会产生冲突.

冲突(coflic)是什么了,怎么能避免呢?

从两个分支的父亲v0.1看起,我们每次改动一个保存文件就会产生一个modify(修改)的动作,我们假如分支里面都对同一个文件产生了modify(修改)动作,当我们合并的时候这就是一个冲突,git无法理解采用哪个分支的modify动作,这时候就要你人工来修改采用哪个分支.

假如没有相同的文件有modify(修改)动作,git就会聪明的知道采用每个分支的最新的modify(修改)修改出一份所以文件的最新版.

那我们怎么来避免这个冲突呢,这就要求我们分支要分的合理,分支只要完成特定的工作,不要越俎代庖,那有些人会说我这个分支一定要改父的耦合地方否则我的代码工作不了,这时我们要好好思考自己的分支的功能,把合并耦合的代码放在主分支里面,次分支只要完成特定功能就可以了,这样合并分支时候不但可以安全的merge(合并)了, 而且修改bug的时候也可以对症下药,直接在问题开始的地方修改.

在牛客网刷了16道题了,在这做个总结

概况

编程题无非两种一种考算法,一种考数据结构

算法的话,考验你对事情的分析程度和脑袋的灵光,用好的算法还是又大又重的算法,用算法复杂度来看,一般能到 o(n)就算勉强可以,当到了o(n * n)你就要考虑是不是你算法有问题了.

数据结构的话,队列和链表和二叉树是比较常见的,当然有些奇怪的一般算法反倒很简单.

算法

谈算法的话不得不谈递归了,递归其实我感觉更想一种思想

话不多说请看题,斐波那契数列一直是递归的代表

                      0                 n= 1    
f(n) ={           1                   n= 2
            f(n-1) + f(n-2)     n>2

虽然

因为这段时间待在家没什么事干,虽然完成一个小的项目,也还有很多功能需要完善,但是说实话还是有点迷茫,明年就大三了,接下来有两条路给我走,要么是考研,要么是出去工作.
自己算是个半个程序员吧,web开发那一套前后端撸起袖子都能搞出点名堂来,但是为什么说我是半个程序员呢,因为现在我还不知道我是爱他还是不爱他呢.

兴趣这东西是培养来的,谁也不是天生就死心塌地的爱上某个东西.

现在学习新东西的成本太低了,这两个月来我也尝试过很多以前没有接触的东西,Android,Deskapp,用了几个框架做了几个apk和软件,虽然用前端栈实现的东西但是所以的语言都差不多.

当然项目基本功能都很单一,当然复杂的也就是简单的堆积而成.

完成之后反倒更迷茫了,为什么了,互联网技术太好学了,无论你的文化程度有多低,按照demo依葫芦画瓢搞几下也能像模像样,我们作为一个文化程度比较高的一群人,或许花半天就能比别人一天能掌握的东西.

但是人家索要的报酬却比你的四分之一还要低,你拿什么跟人家竞争

我有一些志同道合的同学虽然像我一样学了几年web这一块,但是他们决定考研,web谁都能做,不用读大学就能做,他们想多读点出来改造世界,也可以说让世界更美好吧.

其实我对考研的态度是消极的,我现在嘛并不准备投身科研这方面,在美国其实大学都是少部分人才能上,比起我们现在博士硕士满天飞,在美国其实只有真正喜欢科研的人才会去读大学.

其实我写的时候很纠结,该怎么写才不跑题……

我其实想总结的是注重基础,但是我脑海里面出现无数个论点,好像回到了高中时代写议论文了, 想把主人公塑造成一个焕然大悟顿悟人生的形象…..

以上是个小插曲跳过跳过

现在开始正式总结了,越短越好.

总结

七八两个月自己还是很急躁的两个月,一方面在于选择技术栈,一方面在于选择出路.

虽然没有想通,但是在想的过程中发现自己好焦躁…….

总是想一秒钟决定自己未来N年的时,有点像人生规划一样

虽然说搞个人生规划很好,但是发现真的是白费力气,因为无论那个伟人,谁也不知道自己将来会干什么,奥巴马在大学客堂睡觉的时候也不知道自己将领导美国.

焦躁点找到了,接下来就静下来想了怎么解决了,既然无法预测未来那就好好准备未来吧.

怎么准备呢?做你不屑做的事,并坚持下去.

你们不要想歪了,不屑做的事是指那些你看起来没什么卵用的建议.

比如说每天背几个单词,每天看一点英文原著,每天刷一刷编程题

在这我要提一句,虽然很多人从高中过来有点嫌弃刷题了,但是我们是大学生不能用高中生的观点去看待问题,高中的刷题是为了分数,大学的刷题是为了醒脑

你看不起一天在leetcode刷几道题,那是因为你刷不动,不愿刷,以前刷题刷到你一眼就能看出答案,现在我们刷题要每刷一遍都要有新的感受,让大脑活跃起来.

而且当年把运动刷题背单词看书写博客当成副本规定题量字数的时候,每次完成你的任务都会很爽,一方面脑子变得活跃了,另一方面你有种马上就升级的感觉.

每天都能发现自己的进步这是你前进的动力,虽然我们无法预测未来,好好准备,当未来真的来的时候,不会慌.

私下插个话,现在app泛滥,你刷单词完全可以搞个扇贝,刷题搞个牛客或leetcode,健身搞个keep,虽然我没收他们的广告费,但是这些东西给我的反馈比我自己搞个小本记着好多了.不多说了与君共勉,希望大家都能沉下焦躁的心,一起慢慢”升级”.

翻译自TDD-byexample
作者Kent Beck, Three Rivers Institute
有删减

表现

测试驱动开发核心:

  1. 除非你有失败的自动化测试千万不要写一行新代码
  2. 拒绝重复

这两个的简单原则构成了TDD的核心,但是他能规划一个复杂的项目乃至一个团队.这里有一些TDD的建议.

  • 你的项目设计不能太过全面,只要有一个模型或者相应的功能,然后你让你的测试代码测试你模型,通过反馈来完善你设计.
  • 你必须自己写测试代码,你不能依靠别人来每天帮你修改无数次测试
  • 你的开发环境必须能监控到代码的微小的变化
  • 你测试代码必须要非常简单,复杂的测试代码说明你的程序有问题

根据核心我们总结了一种具体的测试方法:红绿重构法

红 --- 写一段测试代码让他无法通过,有时候可以编译都通过不了
绿 --- 写尽可能少的代码让测试代码通过,通过后保存一下系统状态
重构 ----删掉所以重复的代码只要让测试代码还能通过

红绿重构法是TDD最高作战计划,他看起来很笼统,其实他具体到了每一行代码.

如果我们按照TDD这种开发模式,我们会有什么好处呢

  • 如果我们新功能出了问题,质检部门能很快的将新代码回归到上一个稳定代码,尽快避免损失
  • 如果代码的测试能将”惊喜”挖的差不多的话,项目经理能更准确的知道客户在使用过程中碰到的各种奇葩问题
  • 如果我们的测试能够让各种技术交流变得更加清晰,我们能更快的进行技术排错
  • 如果我们项目的bug更少的话,我们的项目能变得更加灵活,我们可以很轻松的添加新功能呈现给我们顾客

这些概念看起来很简单,但是我们的动机呢?为什么我要在写代码过程添加自动测试?为什么我要每次迈小小的一步在我能够做到更多的情况下?两个字:担忧

担忧

测试驱动开发是一种管理你担忧的一种编程方式,担忧也不是说就是坏东西,自信过头也不好,但是恐惧给你一种在项目开头时,”我看不到这个项目的能够完成”,这种感觉,

假如说痛给你一个”停下来”的信号,担忧给你一个”要认真”的提醒信号,但是与之而来的,担忧带来一些你消极的影响

  • 让你变得迟疑
  • 让你开始抱怨
  • 让你开始不愿交流
  • 让你开始不想接受反馈

这些没有一个对你编程有帮助,尤其是当你在编一个比较复杂的软件的时候,所以你怎么来面对这些呢

  • 抛弃迟疑,学习快速有效的编程
  • 不要拖沓,跟别人交流思路要清晰
  • 不要拒绝反馈,去寻找更多有帮助的反馈

想象一下编程就是你提桶着水过河,当你水桶很小的时候,你轻微的震动没什么影响,但是当你的水桶很大,而且水很满的时候,你会很累,你无时无刻不在担心你的水是否会撒掉.

这个时候你需要一个运水的管道,每当你用水桶打一点水,你可以把他放进管道,确保这点水安全到达对面,继续打水.

这个测试驱动开发中的测试就是运水的管道,一旦你的测试通过,你就知道水已经送过去了,不需要担心水到不到对岸了,你就这样一步一步让所以的工作正常进行,但你测试失败的时候,专注于让他通过,这样下一个下一个,慢慢的我们接触到了编程难题,你的测试慢慢覆盖到整个项目.

TDD给你一种控制的能力.当年在外面开车碰到下雪,你可以迅速停车去做其他琐事,当雪停了,你可以继续开车.

所以很多人说他们使用TDD对于项目的变化更有控制力.

接下来我会用一个例子来详细的介绍TDD开发的流程.

由于原作者是用java来介绍的,本人用Python较多,所以就用自己写的一个项目sample来做介绍,详细链接
接下来翻译一下书后面关于TDD的一些答疑

TDD答疑

我希望在这里提出一些或大或小问题帮助你思考如何将TDD引入到你的个人实践里面

你的测试步伐到底该多大?

这里有两个引申过来的问题

  • 每个测试覆盖范围该多大
  • 当你重构时到底有要迈多大步伐

你的测试可以覆盖到你写的每一行代码和你每下一构,你的测试也可以覆盖你上百行代码和你几个小时后的重构,但是哪个才是我们该写的测试呢.

从某方面来看,你应该要能做到其中一个测试,虽然TDD宗旨就是每一步都非常清晰,每一步伐都要求非常小,但是我们对软件驱动开发的经验会使这个步伐或多或少产生影响.

当你开始重构的时候,你应该准备好将一个重构分成很多小的步伐.重构很有可能会发生错误,你把坑都踩一遍然后填上,然后你接下来重构的可能性就会小很多.一旦你完成每次用多个小步伐的一个重构,你可以试试遗漏一些步骤.

你需要多少反馈