当前课程知识点:软件工程 > 第2章 编写高质量代码 > 2.6 结对编程实践 > 讲课视频
前面我们讲过
代码复审是一种用以
确认方案设计
和检查代码缺陷的有效手段
那么为什么我们不把这种
好的方法用到极致呢
结对编程就是把编程和复审
进行有机的结合
它是敏捷开发方法中
极限编程所大力提倡的
一种实践
简单的说
结对编程就是两名程序员
并排坐在一台电脑前一起工作
他们一起分析问题
一起设计程序
一起写测试用例
一起编程实现
一起做单元测试和集成测试
一起写文档
这种方法有助于提升
代码的质量
提高开发效率
同时还可以促进团队能力的
提升和知识的传播
在现实生活中
类似的搭档关系比比皆是
例如越野赛车里面的驾驶员
和领航员
飞机上的驾驶和副驾驶
战斗机编组里面的长机和僚机
这些任务都有一个共同的特点
就是他们都是在高速度中
完成任务
而且任务有比较高的技术
要求任务失败的代价都很高
结对编程也有两个角色
一个是驾驶员
负责用键盘编写程序
另一个是领航员
起到领航和提醒的作用
其中驾驶员的主要任务
是对程序进行设计
编写代码
并进行单元测试
领航员的任务是
检查驾驶员的工作
考虑单元测试的覆盖程度
以及代码是否需要修改完善
同时帮助驾驶员
解决具体的技术问题
比如查询API手册等
在编程过程中
两个人的角色是轮流互换的
结对编程不仅仅涉及编程活动
也包括分析 设计 测试等
全程活动
结对编程需要两个人
不断地会话和讨论
驾驶员需要不停地向伙伴
解释自己的做法和想法
如果发现自己的伙伴
不能够投入
就需要及时的沟通
领航员需要时时提出自己的
疑问和意见
指出可能存在的问题
因此口渴的指数
是核实伙伴交流程度的
一个考核标准
在结对编程过程中
领航员来控制开发的时间
在合适的时候提出来休息
或者提议交换角色
当然驾驶员感到累的时候
也可以主动要求换角色
领航员需要注意的是
要给驾驶员一点时间
去发现和找到自己的错误
不要让对方觉得你很烦
常用的资料 规范以及书籍
都应该放在手边
这样方便领航员快速的查阅
以提供帮助
在结对开始之前
双方要协调沟通
互相通告希望对方关注些什么
自己喜欢做什么等
要主动地参与
任何的一个任务都是
共同的责任
没有你的我的这样的代码
只有我们共同的代码
要坚持代码的标准
和流程的规范
要注意倾听伙伴的意见
有效的结对编程
并不是一天就能做到的
它是一个相互学习
相互磨合的渐进过程
开发人员需要一定的时间
来适应这种新的开发模式
刚一开始的时候
结对编程有可能不如
单独开发效率高
但是在度过了学习阶段之后
结对编程的开发质量和
开发时间
通常会比两个人单独开发
有明显的改善
这里需要说明的是
并不是所有的项目
都适合结对编程
对于探索阶段的项目
一个人单独钻研更为有效
在后期维护时
如果维护的技术含量不高
只需要做有效的复审就可以
如果验证测试
需要运行很长时间
那么两个人在那儿等结果
确实也是浪费时间
团队成员在多个项目中工作时
难以保证足够的结对编程时间
这样成员就经常会需要等待
反而影响效率
结对编程的关键是如何
最大限度地发挥领航员的作用
如果领航的用处不大
也就没有必要结对了
另外也不是所有的人
都适合结对编程
像不合格的程序员
不合群的程序员
以及抵触结对编程的程序员
都很难适合这种工作方式
现在大家已经对结对编程
有了初步的了解
下面由两位程序员
来演示一下
他们是如何结对编写
生命游戏的
大家好 我是赵雷彧
大家好 我是王昳晗
今天我们给大家演示一下
结对编程在实际应用中的例子
我们会以大家耳熟能详的
生命游戏中的地图模块为例
在实际的编写过程中
给大家展示一下
结对编程是怎么样的
并体现它的优点
在编制它的过程中
我就是那个写代码的
我是负责观察的
首先我们先看一下整个
这个东西应该
整个这个模块已经有了接口
和它们需要的流程吧
我们现在是要实现这个类
然后首先这个类的话
大家看一下它这个接口
它有两个属性
是它地图的属性
然后还有用来重
因为这应该是用来重设地图的
根据这个注释的作用
然后这个应该是对某一个
具体的那个位置的细胞状态
进行设定
后面这些应该是跟
输入输出有关
以及地图变更有关的
然后就并没有太大那个
然后我们现在先讨论一下整个
就是每一个接口
需要有的东西吧
好 首先我们一个一个接口的
来看吧
我们首先是构造函数
构造函数这里可以看到它只有
两个参数
一个是行 一个是列
是的 然后
而且我们可以看到
在下面是它行和列
以属性的形式呈现出来
并且它的属性是只有读取属性
没有设置的
所以说可以看出
这个在构造函数确定之后
是只读的
对的
所以我们应该在构造的时候
就把它的那个合法性判定
给做了
对
所以在这里注释中
我们先写入一些
参数的合法性
然后对于构造函数来说
可能它传下来的参数
并不是我们想要的那种
比如说我们可能对于行数
和列数来说
只能要一个整数
所以说我们需要在这个函数
实现中判断一下
是不是我们需要的类型
对
然后还有就是它传过来
如果是零或者负数的话
我们也需要对它进行判定
对对 是这意思
然后除此之外
哦 对 还有一个事
就是为了防止这个类
已经建立了之后
外面的使用者
就是在没有对它进行reshuffle之前就采取读
或者写的操作
我们是不是应该在这儿
先初始建立一张地图
比如说我们可以调用一个reshuffle
或者是我们先建立
一张空白地图
我倾向于建立一个空白的吧
行
好
然后这两函数都没什么好说的
因为都是只读的
我们直接返回就行
对
应该不需要任何判定
对
然后reset它应该是用
random为来填充整个地图
然后它传进来一个参数
possibility
所以说我们还是要对它进行
参数的校验
也叫合法性的判断 对
possibility一定要是一个float吧
比如说
然后是0到1了
是的
然后除此之外应该没有什么
可以那个了
对
然后下面就get和set
get和set首先
因为它是对某一个位置
进行确定的嘛
所以说自然我们要
就是我们要确定这个位置
是整数
并且是在整个地图的
下标范畴之内
是的 也就是说也依旧需要
进行参数合法性的判断
对
然后但对于set来说
val是不是应该有别的那个
对 val
所谓的val在地图上来说
就是代表的细胞的状态
也就是说
只可能是死或者活
这两种状态
所以说val可能也是
只有两种值的
对 看这个上面注释的话
应该是只能是0和1
所以说这个就简单
判定一下就行
对
那么0和1实际上我们可以
规定成一种常量
就是作为一种
类里面的常量进行对外的调用
可以
然后get neighbor count
这个东西是获取在某行某列的
一个指附近的那个各种元素的
那样各种活的细胞的数目
对
然后它除了需要判定这两个的
合法性之外
似乎也没有什么特别明显
明显需要的东西
干的东西
在具体实现上
可以到时候再讨论
对 可以可以
然后这个就不说了
什么参数都没有
它只是把上一个
neighbor count
对每一个那个跑一遍
对
然后set map的话
它的意思似乎是
传进来一张新的地图
然后把新的地图直接覆盖成
那一张新的
对于这个map的参数来说的话
实际上判断它还是存在一些
应该是诸多的限制因为
对
怎么说呢
首先我们
因为我们这个类的地图尺寸
是固定的
这是不能调的
所以说我们应该
让这个map具有跟这个地图
初始的时候相同的属性
对
然后完了我们还要判定
map中的每一个元素是
跟这个val是一致的
是的
还需要判断它的数据格式
是符合map的样子的
所以这里应该是一个比较
麻烦的东西
所以说对于map来说
一定要是一个list
然后map中的每一个元素
也一定要是一个list
然后
而且对于它的元素来说
它的第一维的维数应该是row
然后每一个那个都应该是
rows
对
都应该是cols
然后主要就是这样
对
然后
下面是print map了
print map的话
关于这个print map这个函数
它传了两个都有默认值的参数
第一个的话
它应该是从那个细胞状态
0 1这两个状态
到一个具体的打印值的
一个映射
所以说我感觉它默认值
如果这样设并不太好
我觉得如果这样会不会好一些
对 就是默认
如果是活细胞就打印1
如果是死细胞就打印0这样
对 然后
如果是按这个格式来的话
我们应该让cell maps
变成一个
是一个list
然后它的维数至少是
要大于那个的
它是大于等于2的
大于等于2的
然后 另外的话
是大于等于2还是
还是就规定为2呢
应该等于2吧
也可以
因为这
毕竟只有两状态
对 可以直接让它等于2
然后这儿是分隔符
分隔符就是在每个细胞
里面那个
它默认是一个空格
这个你只要是个srting就好
对
是一个长度为1的string
或者不一定
不一定为1 都可以的
就行
好 也就是说我们刚才就是
做了一些每一个接口的参数的
一些限定
那我们可以看到
很多的参数实际上都是要
规定它的类型
规定它的range
所以我们实际上可以把
判断类型 判断range
这个一整套工作
作为一个函数来进行封装
那我们应该可以
首先单独提出来一个函数
来检验整数的合法性
就是首先它要是一个整数
并且要是可以那个
处于某个范围中的整数
如果 因为这个东西它并
它的重用性很强
而且对类没有特定的要求
所以说我们大体把它变成一个
固定的静态类型
对
然后参数的话
首先一个是
带检查的那个number
然后我们可以设一个上下限
但是上下限可以那个的
可以为空的
这样方便row扩展
对
可以有一个min
还有一个max
然后这个就行了
然后另外就是判定你一个单元
是不是有效的状态
这个我们还需不需要
单独写一个函数
尽管我们知道它是非0即1
但是我觉得如果直接用这个
函数检查
是不是有点不具有语义性
对 因为0和1实际上对外
并不是一个非常直观的东西
就是说我们还是需要
在这儿进行一些
所以进行一些常量的定义
然后
空格
然后中间空格
好 我们讨论完成之后
开始写代码
但是在实际的结对编程过程中
因为经常写代码的和观察者
会交换角色
所以说现在我和王昳晗
就交换角色
首先我们来实现一下这个
check integer这儿的函数
对
最开始我们要判断
传入的三个参数都是int类型的
是的
所以然后就是
isinstance
isinstance
int或者是
isinstance
如果是出现这三个
不是两种情况
那么我们就抛出type error
就抛出一个TypeError吧
好 OK
然后接下来我们干的事
应该是检查n在范畴之内
首先我们对min和max是否
为none
然后判断n是否在
满足那个条件
对
在那个min和max之间
那么就是如果min
并且
n是小于min的
那么就要raise
这时候raise TypeError
应该不太合适
对
我们是要自己定义一个
exception么
那么我们可以自己定义一个
exception
比如认为它是RangeError
嗯 我们先写上
看到了
刚才多点了个点
min ok
我们之后再
再去补一下
用相同的方式来进一步
判定max
这儿是max
就n大于max
它应该
max
max
我们下一步应该就是定义
一下raise
RangeError
我们之前已经实现了一个
RangeError
然后我们略过中间实现过程
然后现在我们把import进来
然后直接用就行了
那接下来我们实现
下一个模块吧
好 下面我们就可以来实现
构造函数
是的
构造函数
首先我们要对传入的参数
进行一个合法性的检测
对
那我们就调用之前
我们实现的check integer
然后row
这儿应该是rows
min应该是1
至少1好了
对 至少要有1行
max不设
max可以不要 对
cols也是一样的
cols也是1
接下来我们应该建立一个
内部的存储
把这两个值给存进来
比如说叫做什么呢
size吧
就叫size
它就先是等于一个行数
然后等于一个列数
接下来我们应该写一个那个
地图的数组来存储细胞状态
对
就是用一个map来描述
所有的细胞状态
它应该就是一个二维数组的
形式
嗯
那么我们可以初始化
它全都是死细胞
这个记着用上面定义好的常量
对
那就是CELL
CELL DEAD
CELL DEAD
然后对于一个in range cols
我们可以看到这里有一个
错误提示
对 没有使用
我们用下划线代替
没有使用
那么我们可以直接用
下划线代替
代替也一样是rows维度的
好 我们先来实现print map
我们已经规定了
我们需要首先执行的类型检查
就是cell map一定要
是要是一个长度为2的list
那么
if
if
isinstance
not
not isinstance cell
maps
list
这儿是list
对
for
也行 这儿这样
那么就raise
这前面可以加或其实
把长度也变成0
如果 如果你要把
想定义成不同的错误类型的话
就分开
我觉得可以
可以直接遗弃吧
那就or
or
len
not len cell maps
如果它不等于2 对
那么raise TypeError
TypeError
我们可以这里明确的指示
就是cell maps
好 那sep也一样的进行判断
sep只需要判断它
是一个字符串就可以
长度不受限制
对
它应该是
str
str
ok
可以再拷贝(20:57)
好 接下来我们就可以
进行一个打印
对
打印的话我们
用两个for就好了
一行一行打印
对
for row in range self点
rows 点 yes
for row
for cols in range self.cols
那个col不要加s吧 最好
哦 对
因为只有1
对
那我们怎么print呢
需要注意应该是
因为print每次后面要手动
加一个东西
如果不加的话
就需要特殊判定
你这个元素是不是每一行
最后的一个元素
挺复杂的
我觉得我们可以用join
也就是说每一行
因为它每一行都已经是一个
单独的数组了
对
但是那个数组是一个int了
而且int只能是0到1的一个值
所以说可以直接当做
cell map的下标来使
应该建立一个0到1的下标
和那个输出的
和输出cell map的一个映射
对 我们可以用lambda表达式
用lambda表达式
然后用join
对 然后再join
先print吧
对
然后应该是join
应该是sep.join
然后map int
map
嗯 第一个应该是个函数
就lambda表达式
一个是lambda
lambda
然后先不写
然后后面的应该是
应该是
self
self 地图的那个
self map row
然后lambda怎么写
lambda应该是
从x到cell maps[x]
是 应该我们就行了
好 那我们来测试一下
嗯 就直接简单地来print一次
好 我们直接在下面
新建一个类
然后现在它应该全是0
对 比如说是5个
对 无所谓
然后来print一下
game map print
然后我们跑一下
好 我们来跑一下
哦 错了 17行
17行它说这里有错
哦 有一个问题就是
那个min和max可能是none
但是none并不是int
那么我们可以把这里换一下
比如说or min and这个
or
or这个
and这个
max
max and
and这个
然后要一个括号
应该是不需要括号的
因为and比or优先级高
我觉得加上括号可读性
可能会更强一点
行 没问题
那就这样
我们再跑一下试试 好
ok
我们得到了正确的结果
现在我们继续实现下面的函数
由于时间关系
我们刚刚在私下完成了
其他大部分接口的实现
留下了最后两个
最重要的接口
分别是那个
get neighbor count
也就是获取某行某列
一个细胞周围8格的
活细胞数目
以及生成一张那个
就是活细胞数目的地图
也就是对每一个位置
应用上面一个函数
嗯
然后我们大家先可以开始写了
另外提一句
就是我们又交换了
互换了角色
然后现在由赵雷彧
来写代码
是的
然后我们首先干第一件事
还是检查参数的合法性
对
还是用我们之前的
实现的check integer row
要是从0
因为是数组下标
应该从0开始
从0开始到rows减1
然后
对 col也是一样的
cols减1
ok
然后我们现在就是要
记录某一个位置四
应该是四周8格的那个
活细胞数目
但是我们应该怎么写呢
优雅一些
应该是八连通的话
对于每一个格子来说
都是它相邻的8个格子
所以我们可以用一种
比如类似于方向的东西
来规定它
每个方向可以
对 你可以枚举
然后用一个数组
但是如果为了那个
语义性好一点的话
我就用
可以用一个字典来做
对
比如说我们可以这样
DIRECTION
对 实际上每一个方向都是
一个对下标的操作
是的
比如说上的话应该是
row减1 col不变
对
然后
对于
下
下
应该是row加1 col不变
这儿应该用逗号
然后左就是0 1
哦 0 负1
0 负1 对
右 0 1
然后
接着上左
四个角 对
对
是负1 负1
上左 对
然后 上右
是负1 1
对
然后是下左
1 负1
和下右
对
规定好了这8个方向以后
我们就可以去遍历这个字典了
对 我们就for了一个in
DIRECTION
接下来我们要考虑一个事情
就是由于在边界的细胞
它们的判定是穿越边界的
也就是说最左边的细胞的
最左边
相当于是地图最右边的细胞
对
然后
因为对于负数的下标的话
Python是自己可以处理的
但是对于正数超过了下标
我们是不是应该手动取个模
对 其实模一下其实就可以
对 所以说我们应该临时的
取一个trow等于row加
direction dire
然后取一个模
对
模自己的行数
对 row在加的时候
应该是加direction的
direction的0
第一个
对对对
然后同0做了一个col
对
然后这个应该是自己的列数
好 然后
在这之前我们应该确定一个
能返回的值
counter 初始化为0
然后
那么进行一些判断
对
如果在自己的地图的
这个位置
位置的元素
tcol
是那个
如果是活的
对
我们之前定义过的一个常量
应该counter加
counter加
好 接下来就要返回
counter应该就够了 对吧
对
代码分隔
加一些空格
然后上面
上面要相等
好的
在实现这个函数之后
我们就应该可以实现
下来这个函数
对 这个就是对外的
一个调用的函数
这个只需要返回一个二维的
数组
所以说我们需要
这个函数本质上就是
遍历map里的每一个格子
然后生成每一个格子的那个
里面的活着的数量
是的
所以我们只需要简单的
二重遍历
对
二重循环
self.rows
然后我们需要弄一个临时的行
再遍历第二个cols
然后我们需要
每一个都要判断进去
对 我们需要把得到的结果
给压进去
self点 点neighbor count
neighbor count
row col
对的
然后没完成这一步之后
我们需要把得到的这个
这个数组
append进去
最终返回整个数组
对
这个函数应该还是比较显然的
是的
好 那我们应该也
也就已经完成了整个生命游戏
所需要的全部函数
对 我们
我们测试一下整个程序吧
对
由于main函数什么的
是已经提供好的
我们可以直接调用
直接在python里面写一下
看看结果
我们可以看到它正在迭代
对 然后规则的话
应该也是按照我们预想的
在执行
大体上没有问题
然后这就是结对编程
在编写生命游戏的
一个模块中的一个具体实现
嗯 是这样的
-1.1 软件无处不在
--讲课视频
-1.2 软件的本质特性
--讲授视频
-1.3 软件工程的产生与发展
--讲授视频
-1.4 软件工程的基本概念
--讲授视频
-1.5 软件质量实现
--讲授视频
-1.6 业界人士谈软件工程
-测验题--作业
-讨论题
--讨论题
-作业题
--第一张 作业题
-2.1 编程过程与规范
--讲课视频
-2.2 良好的编程实践
--讲课视频
-2.3 Python集成开发环境
--讲课视频
-2.4 代码静态检查
--讲课视频
-2.5 代码性能分析
--讲课视频
-2.6 结对编程实践
--讲课视频
-2.7 刘贺谈软件工程
--讲课视频
--讨论
-测验题--作业
-作业题
--第二章 作业题
-3.1 单元测试概述
--讲课视频
-3.2 黑盒测试方法
--黑盒测试方法
-3.3 白盒测试方法
--基本概念
--代码覆盖标准
--基本路径测试
-3.4 单元测试工具
--单元测试工具
--html
-测验题--作业
-作业题
--第三章 作业题
--作业题附件
-4.1 软件过程
--讲课视频
-4.2 软件过程模型
--讲课视频
-4.3 敏捷开发过程
--讲课视频
-4.4 微软公司开发过程
--邹欣经理自我介绍
--微软开发过程之一
--微软开发过程之二
-测验题--作业
-5.1 团队组织与管理
--讲课视频
-5.2 项目沟通管理
--讲课视频
-5.3 软件项目计划
--讲课视频
-5.4 软件项目估算
--讲课视频
-测验题--作业
-讨论题
--讨论
-6.1 敏捷开发之Scrum
-- 敏捷开发之Scrum
--html
-6.2 用户故事与估算
--讲课视频
-6.3 团队协作工具Tower
-6.4 配置管理
--讲课视频
-6.5 配置管理工具Git
--讲课视频
-测验题--作业
-作业题--作业
-7.1 需求工程师
--讲课视频
-7.2 需求定义
--讲课视频
-7.3 需求的类型
--讲课视频
--讲课视频(2)
-7.4 需求工程过程
--讲课视频
-7.5 需求的主要来源
--讲课视频
-7.6 需求获取技术
--讲课视频
--讲课视频二
--讲课视频三
-7.7 撰写需求文档
--讲课视频
-测验题--作业
-讨论题
--讨论
-8.1 用例建模概念
--讲课视频
-8.2 用例建模过程
--讲课视频
-8.3 用例建模精讲
--讲课视频
-8.4 建模工具介绍
--讲课视频
-8.5 微信抢票应用案例
--讲课视频
-测验题--作业
-讨论题
--讨论
-9.1 面向对象分析
--讲课视频
-9.2 CRC卡片分拣法
--讲课视频-1
--讲课视频-2
-9.3 面向对象设计
--讲课视频-1
--讲课视频-2
-9.4 类图建模
--讲课视频-1
--讲课视频-2
-第9章 面向对象分析与设计--测验题
-讨论题
--讨论
-10.1 顺序图概念
--讲课视频
-10.2 顺序图建模
--讲课视频
-10.3 顺序图风格
--讲义视频
-10.4 状态建模
--讲课视频
-10.5 状态图
--讲课视频
-10.6 状态图精讲
--讲义视频
-测验题--作业
-讨论题
--讨论
-11.1 软件体系结构概念
--讲授视频
-11.2 软件设计原则
--讲授视频
-11.3 软件体系结构风格(一)
--讲授视频
-11.4 软件体系结构风格(二)
--讲授视频
-11.5 软件体系结构风格(三)
--讲授视频
-11.6 软件设计过程
--讲授视频
-11.7 Web系统架构设计
--讲授视频
-11.8 数据库选择策略
--讲授视频
-测验题--作业
-作业题
--html
--html
--html
-作业题--作业
-12.1 交互设计概述
--讲授视频
-12.2 交互设计目标
--讲授视频
-12.3 GUI设计原则
--讲课视频
-12.4 KLM效率模型
--Video
-12.5 Fitts定律
--讲授视频
-12.6 交互设计过程
--讲授视频
-测验题--作业
-13.1 软件测试概念
--讲课视频
-13.2 软件测试类型
--讲课视频
-13.3 软件功能测试
--讲课视频
-13.4 软件性能测试
--讲课视频
-测验题--作业
-14.1 软件部署与交付
--讲课视频
-14.2 软件演化与维护
--讲课视频
-测验题--作业
-第一部分:基础知识
-第二部分:编程与测试(选做)