读程序员的制胜技笔记11_与Bug共存(上)

发布时间 2023-11-13 06:52:43作者: 躺柒

1. bug只与规则有关

1.1. 如果根本没有任何规则,那么bug也就不存在了

1.2. 公司也就可以不用拙劣的借口“哦,那只是一个feature”来做危机公关

1.3. 你不需要为规则写一份书面文档——你可以只让它存在于自己的脑海里

1.4. Bug是衡量软件质量的基准参考

2. 类型有大用

2.1. 快速并不仅仅涉及代码编写速度,你还得算上代码维护的速度

2.2. 软件开发是一场马拉松,而不是短跑

2.3. 指定数据类型是在编程中防止数据编码出现冲突的最早防范措施之一

2.4. 类型让你尽早受挫,让你在代码中的隐患酿成大错之前,修复它们

2.5. 使用强类型

2.5.1. 类型检查可以算是对代码正确性的免费初审

2.5.2. 好好吃透底层类型系统会让你在成为资深开发者的路上走得更悠然

2.5.3. 类型可以帮助我们编写更安全、更快速运行、更容易维护的代码

2.6. 使用解释型编程语言编写代码的速度非常快,因为你并不需要真正地去声明变量的类型

2.7. 有效性证明

2.7.1. 有效性证明算是预定义类型较少为人所知的一种好处

2.7.2. 数据验证并不能给全部代码的有效性证明“打包票”

2.7.3. 类型可以承载有效性证明

2.7.3.1. 在构造时验证其有效性,这样一来就不可能包含无效值

2.7.4. 运算符重载,非必要不启用

2.8. 奥尔曼(Allman)样式

2.8.1. 每个花括号都在自己专属的行上

2.8.2. 官方推荐使用1TBS(一种真正的花括号样式),也就是改进的K&R样式

2.8.2.1. 其主张一个花括号与声明在同一行

2.9. 巧用框架

2.9.1. 一些由你自定义的、基于文本的值,比如URL、IP地址、文件名,甚至日期,这些都存储为字符串

2.9.1.1. 看看这些现成的类型

2.9.2. IPAddress比string类型更合适存储IP地址,不仅因为它有验证功能,而且它对现已投入使用的IPv6的支持也非常不错

2.9.3. TimeSpan

2.9.3.1. 它代表持续时间

2.9.3.2. 你没有理由用TimeSpan以外的任何东西来表示持续时间,即使你所调用的函数不接受TimeSpan作为参数

2.9.4. 总是尽量使用DateTimeOffset而不是DateTime

2.9.4.1. 它也很容易转换为DateTime

2.9.4.2. 可以使用算术运算符操作TimeSpan和DateTimeOffset,这要归功于运算符重载

2.9.5. 乔恩·斯基特(Jon Skeet)的Noda Time

2.10. 用类型防止打错字

2.11. null的可与不可

2.11.1. “十亿美元的错误”

2.11.1.1. 托尼·霍尔(Tony Hoare)

2.11.1.1.1. null的发明者

2.11.2. null,某些语言中写作nil,是一个值,它代表着没有值或程序员的冷漠

2.11.3. 由于具有0值的内存地址意味着内存中的无效区域,现代CPU可以捕获无效访问并将其转换为对人类友好的异常消息

2.11.4. 可空引用

2.11.4.1. 当所有引用类型都可以为null的时候,所有接受引用的函数都可以接收两个不同的值:有效引用和null

2.11.5. is运算符不能被重载

2.11.6. 如果问题及早暴露,查看异常的栈跟踪时,会发现它非常干净,方便排查

2.11.6.1. 你能确切地知道是哪个参数导致函数运行失败

2.11.7. 当你没办法而非得使用空字符串时,不要使用 ""符号来表示空字符串

2.11.7.1. 很容易将其与一个带有单个空格的字符串(" ")混淆

2.11.7.2. 可以用String.Empty表示,利用现有类型

2.11.8. C# 9.0中引入了一个新的结构,名叫记录类型(record type)

2.11.8.1. 创建一个不可变的类

2.11.9. Maybe<T>已死,Nullable<T>永恒

2.11.9.1. 编译器既能检查又能优化nullable类型,比临时的实现更好

2.11.9.2. 你还可以从语言中得到运算符和模式匹配的语法支持

2.11.10. null检查有助于你思考正在写的代码的作用,你会更加清楚地知道值到底是不是需要设定为可选的

2.11.10.1. 它会减少bug,让你成为更好的开发者

2.12. 免费的更好性能

2.12.1. 一个有效的IPv6字符串最多可以有65个字符

2.12.2. IPv4地址的长度至少为7个字符

2.12.3. 基于字符串的存储将占用14~130字节,如果包含对象头,则占用30~160字节

2.12.4. PAddress类型将IP地址存储为一系列字节,只使用20~44字节

2.13. 引用类型与值类型

2.13.1. 引用类型和值类型之间的区别主要在于类型在内存中的存储方式

2.13.2. 值类型的内容存储在调用栈中

2.13.2.1. 值类型之所以存在,是因为在某些情况下,它们在存储和性能方面都比引用类型效率更高

2.13.2.1.1. 对于值类型来说,运行时直接读取该值,从而实现更快的访问速度

2.13.3. 引用类型(reference type)存储在堆(heap)中

2.13.3.1. 只有在引用类型内容的时候才会将其存储到调用栈中

2.13.3.2. 引用也导致单层间接性(a single level of indirection)

2.13.3.2.1. 当你需要访问一个引用类型的字段时,.NET运行时必须先读取引用的值,然后转到引用所指向的地址,再从那里读取实际的值

2.13.4. 一个不可变、仅表示数值的类,行内人称为值类型(value type)

2.13.5. 结构在定义上与类(class)非常相似,但与类不同的是,它在任何地方都是通过值传递的

2.13.6. 引用(reference)类似于管理指针(managed pointer)

2.13.6.1. 指针是内存的地址

2.13.7. 虚拟内存(virtual memory)

2.13.8. 垃圾回收

2.13.8.1. 程序员需要注意他们的内存分配,并在完成内存分配后释放分配的内存

2.13.8.2. 不及时释放内存的话,应用程序的内存使用量会不断增加,这也称为内存泄漏

2.13.8.3. 引用计数(reference counting),是第一个被提出用来解决手动内存管理问题的方案

2.13.8.3.1. 是垃圾回收的一种原始形式,运行时将为每个分配的对象保留一个秘密计数器(secret counter),而不是将释放内存的主动权留给程序

3. 旅行推销员问题

3.1. 计算机科学中的一个基础主题

3.2. Traveling Salesperson Problem,TSP

3.3. NP完全问题(NP-complete)

3.3.1. 非确定性多项式时间完全问题(nondeterministic polynomial-time complete)

3.3.2. NP是P(多项式时间)问题的超集,只能用“蛮力”解决

3.3.3. 意味着“我们几乎解决不了这个问题,但我们可以马上验证一个别人建议的解决方案”

3.4. 基于图灵机的编程语言是图灵完全的

3.4.1. 艾伦·图灵(Alan Turing)

3.4.2. 图灵机允许我们在软件开发方面有无限的可能性,但不利用图灵机就无法验证软件开发的正确性

3.4.3. 由于图灵机的性质,错误是不可避免的

3.4.3.1. 不可能有一个没有错误的程序

3.4.3.2. 在你着手开发软件之前,接受这个事实将使你的工作更容易

3.5. 并不依赖图灵机的语言

3.5.1. HTML、XML或正则表达式

3.5.2. 它们能做的事远远比那些图灵完全的语言少

4. 不要修复bug

4.1. 开发团队必须有一个筛选过程,来决定在任何大型项目中要修复哪些bug

4.1.1. 确定bug的优先级

4.2. 评估优先级的一个更简单的方法是先使用一个不相关的第二维度,即严重性

4.3. 优先级是指某个bug对业务的影响

4.3.1. 如果你的主页上的企业logo丢失了,这或许一点都不严重,然而这件事有最高的优先级

4.4. 严重性是指它对客户的影响

4.4.1. 如果你的平台上的一个网页不能工作,这是一个高严重性的问题,因为客户不能使用它

4.4.2. 但它的优先级可能完全不同,这取决于它是在首页还是只有少数客户访问的不起眼的页面

4.5. 你区分的级别越多,区分它们就越困难

4.6. 你应该有关于优先级和严重性的阈值

4.6.1. 任何低于这个值的bug都会被归类为不修复

4.7. 寻找bug也会产生成本

4.7.1. 当务之急是避免关注那些永远不可能被修复的bug

4.8. 试着在遇到bug的时候就赶紧做是否要修复的决定,这既能为你节省时间,而且还能确保你保持合格的产品质量