程序代码写出来是给他人和自己看的,附带才是给机器执行的.
如果你写的代码连你自己几个月后都看不懂,那么说明这代码的水平可太烂了.
计算机科学是个错误的名字,因为它不是计算机的科学,就像外科手术不是刀子的科学。—Dijkstra
1. 编程范式
- Object-Oriented Programming
- 命令式编程
- 函数式编程
函数是函数式编程的核心抽象.尽管需要一定数学基础,扎根于
lamda
演算,但是它可以提供一定程度的代码模块性和可重用性.
2. 代码简洁之道
2.1.1. 代码复用
公共类
新建基类
对于创建一个Sample的实例,将Sample的儿子进行划分,即共同部分封装,不同部分子类实现.可以使得创建实例所需要的大量初始化工作分离而得以简化.对于抽象类与接口两个工具:
- 抽象类能够保证实现的层次关系,避免代码重复,但易导致过于复杂的继承关系
- 接口定义行为模型,能够更有效地分离行为与实现
局部变量转共用的成员变量
传递函数块
函数式接口
参数复用
Curry,柯里化
2.2. 基本操作
- 代码行太多时,每敲完一个模块,选中该模块,化块为行,避免代码行数太多,造成视觉上的紊乱,保持良好的化块为行习惯,提高出问题时的查找效率;1
- 每往下敲一级,用
tab
键缩进一空格,保持代码块的有序排列; - 可以用逻辑运算合并条件判断;
- 利用数学公式/方法从源头重构.比如辗转相除法吊打枚举法;
- 突然发现一个Bug,但是又不想停下来手中的活,以免打断思路,怎么办?可以在代码中加个TODO注释;
- 多写小的代码块,小方法/小功能/小程序
- 命名
- 输入输出尽量给出说明
2.3. CSS
- 敲完
CSS
里边的每一个样式,在样式后边顺手加一个分号(;);1
3. 代码写出之道
感受思考的过程
3.1. 从题目到思路
If Yyou don’t explain something in simple terms, you don’t unserstand it.—Richard Feynman
先是读懂题目才可以,需求→需求分析→设计→编码→测试→交付
采用测试驱动设计(Test Driven Development,2)的方法(问题域)
- 自己给几个简单的测试用例
- 暴力解法
必须注意的是,项目中使用TDD,可能需要大量Mock
子程序桩或者重构代码以使返回对象可测试.
3.1.1. 不暴力的话?
-
用哪种数据类型/结构来组织和保存数据?遍历常见的数据结构
-
用何种算法来计算效率更高?遍历常见的算法思路,比如先排序:O(nlogn)+~
- 你手头有哪些数据结构以及对他们的操作的知识:数组/链表/栈/队列/二叉树/树/图
-
用面向过程/面向对象还是函数设计的编程范式? ---这肯定少不了大量的经验积累,一步一步
3.2. 从思路到步骤
3.3. 从步骤到代码
- 首先得有哪些参与方?程序输入是什么?它最后要什么?过程中涉及到哪些需要标识的东西,或者辅助的?
-
- 明确变量的含义
-
- 循环不变量
一行代码的作用究竟是什么?
原理:栈中有一个int型大小的空间,这个空间保存着42这个值的二进制码,通过num
可以取到这个空间的二进制码并按照整型的方式翻译
当程序运行到某行代码的时候,究竟有多少数据?这些数据的含义是什么?值是怎样被确定的?我该怎样处理它们才能达到程序的目的?
- 其次,边界条件注意了吗?逻辑完全理顺了吗?
对于递归,确定哪些是对初始数据的操作,确保这个操作的正确性.之后递归的自身函数调用时,流程与对初始的操作一致,这时我们假定函数是正确工作的,能够返回正确的结果。
- 不知道程序为什么不能这样运行,以及这样为什么就行了的时候应该怎么办呢?
3.4. 从可执行到健壮
程序主体完成之后,再想想其他的特殊情况能满足吗?然后再优化程序对各种情况的兼容性.
- 各种为空的情况
- 变量名
- 模块化/复用
- 基于特殊情况的避免来节省操作次数
4. 代码读懂之道
4.1. 前提
1. 确保这个代码能运行 2. 大致浏览一遍确保其没有用相对自己特别特殊的方法 3. 使用代码编辑工具,代码高亮方便观看
4.2. 看需求文档
如USE CASE:描述角色与业务流程,正常分支与异常分支等
4.3. 架构文档
主要技术构件,之间是怎么交互的
4.4. 项目本身
关键模块
5. 代码调试之道
调试是把症状和原因联系起来的尚未被人类认识的智力过程.3
The art of debugging is figuring out what you really told your program to do rather than what you thought you told it to do.—Andrew Singer
5.1. 调试为什么这么难
因为软件错误的外部表现和它的内在原因之间可能并没有明显的联系,调试人员只能是猜想一个原因,并设计测试用例来验证这个假设,不断重复此过程直到找到了原因并改正了错误.调试是软件开发过程中最艰巨的脑力劳动.
- 症状和产生症状的原因可能在程序中相距甚远
- 当改正了一个错误之后,症状可能暂时消失了
- 症状可能实际上不是由错误引起的(如舍入误差,内存泄漏等)
- 不易跟踪的人为错误引起
- 定时问题引起而不是处理问题错误
- 可能很难产生完全一样的输入条件(例如,输入顺序不确定的实时引用系统)
- 症状可能时有时无.在硬件和软件紧密耦合的嵌入式系统中很常见
- 可能是由分布在许多任务中的原因引起的
5.2. 科学地调试
先小数据调试
蛮干法
- 到处打log/print⇒这个时候出去休息休息可能更好:-)
回溯法
- 从发现症状的地方开始,人工沿控制流往回追踪分析源程序代码
原因排除法
-
对分查找 如果已经明确每个变量在程序内若干个关键点的正确值,可以用赋值语句或输入语句在程序中点附近**“注入”**这些变量的正确值,然后判断是程序前半部分还是后半部分出了错
-
归纳 组织大量的数据归纳多个原因再一个个排除:
- 查找日志
-
演绎 设想出所有可能的出错原因,然后试图用测试来排除每一个假设的原因 一定有个排除记录!一定有个排除记录!一定有个排除记录!
-
Delete everything & begiin with fresh eyes.
5.3. 向同行求助
如果用尽了调试方法依然不得其因,则应该向同行求助
6. 改正错误警告
改正一个错误可能引入更多的其他错误,所以动手改正错误之前,需要问自己:
- 是否同样的错误在程序其他地方也存在?
- 将要进行的修改可能会引入的”下一个错误”是什么?
- 为防止今后出现类似的错误,应该做什么?
7. Bug避免细节
7.1. 空
7.1.1. 空的类型
//null
String s = null;
空对象是指声明一个对象s,但是没有给该对象分配空间,即没有实例化该对象,因此,空对象在调用所有对象方法时候都会抛出异常。
//空值:
String k = "";
空值是指一个字符串对象已经实例化,即系统已经给该变量分配了空间,只是对象的内容为空。
//空格:
String n = ” ”;
是指一个字符对象已经实例化,对象的内容为空格。
7.1.2. 判断非空
- 传入函数参数的时候,
- 参数可否为空?
- 之后需要访问它的下一个结点或者左右子树⇒不可为空
- 现在控制参数在为空时返回吗?
- 不是递归唯一退出路径⇒不可以⇒可以在函数体内部判断,如果为空则不执行某些可能越界的操作
7.2. 数字处理
7.2.1. 浮点值比较相等
因为计算机是近似存储浮点值,因此直接比较是无意义的.一般通过测试两个数的差小于某个阈值, 来比较他们是否已经足够接近
- 通常将阈值设为
1e-14
来比较两个double类型的值:
7.2.2. Double与int的转换
需要有一方是.0
代表浮点型,以及使用*100/100.0
这样得到两位小数的技巧
7.2.3. 数位的获得
对于两位数,/10
得到十位,%10
得到个位,;
(int)(Math.random()*100+0)
得到一个0到100的随机数
7.3. 字符串处理
7.3.1. 判断字符串相等
string1 == string2
只能判断两个引用是指向同一对象,但无法判断内容:string1.equals(string2)
,返回Boolean值string2.compareTo(string2)
,如果按字典顺序前面小于后面,返回值为差值小于0;
8. 计算思维之先进
8.1. LOOP
9. 项目搭建之道
我觉得前提是你大概知道轮子是怎么造的
多接触现实世界的项目,避免重复造轮子,比较各种开源的轮子优劣选择适合自己的轮子并用高效的代码组合实现需求
9.1. 生命周期各工具
PS2