• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

软件工程基础-软件建构步骤

2021-09-29

1. Ideal Software Construction 理想的软件建构步骤

  1. Problem definition
  2. Requirements development
  3. Construction planning
  4. Software architecture, or high-level design
  5. Detailed design
  6. Coding and debugging
  7. Unit testing
  8. Integration testing
  9. Integration
  10. System testing
  11. Corrective maintenance

2. algorithm vs heuristic(启发)

绝对vs不确定

Here’s a heuristic for getting to someone’s house: Find the last letter we mailed you. Drive to the town in the return address. When you get to town, ask someone where our house is. Everyone knows us—someone will be glad to help you. If you can’t find anyone, call us from a public phone, and we’ll come get you

2.1. metaphor(隐喻)

在软件开发过程中,我们会碰到种类繁多的软件隐喻,例如臭虫(bug)、菜单(menu)、视窗(windows);架构(architecture)、服务(service)、对象(object);黑盒(black box)与白盒(white box);瀑布(waterfall)软件模型、迭代(iterative)软件模型……这些软件隐喻无一不是从日常生活或其他科学领域中借鉴而来,根据我们原有的认知,实现一种类比的定义,并逐步发展为软件领域的固有概念

2.2. 水性沉淀物的隐喻

这并不意味着你必须学会如何用水性沉淀物制作代码;这意味着你必须学会如何每次向你的软件系统添加少量的内容。其他与增殖密切相关的词是 “增量”、”迭代”、”适应性 “和 “进化”。增量设计、构建和测试是现有的一些最强大的软件开发概念。


Incremental designing, building, and testing are some of the most powerful software-development concepts available.

2.3. incremental development 增量开发-骨架构建

In incremental development, you first make the simplest possible version of the system that will run. It doesn’t have to accept realistic input, it doesn’t have to perform realistic manipulations on data, it doesn’t have to produce realistic output—it just has to be a skeleton strong enough to hold the real system as it’s developed.


The image of “building” software is more useful than that of “writing” or “growing” software. It’s compatible with the idea of software accretion and provides more detailed guidance

3. Problem-Definition

The problem definition should be in user language, and the problem should be described from a user’s point of view. It usually should not be stated in technical computer terms

问题确定是foundation,是三角形的底

4. Requirements development

项目需求是经常变化的

Studies at IBM and other companies have found that the average project experiences about a 25 percent change in requirements during development (Boehm 1981, Jones 1994, Jones 2000), which accounts for 70 to 85 percent of the rework on a typical project (Leffingwell 1997, Wiegers 2003).

4.1. Estimating a Construction Schedule

Estimates created early in a project are inherently inaccurate. As the project progresses, estimates can become more accurate. Reestimate periodically throughout a project, and use what you learn during each activity to improve your estimate for the next activity

在项目早期所做的估算本身是不准确的。随着项目的进展,估算可以变得更加准确。在整个项目中定期重新估算,并利用你在每个活动中所学到的知识来改进你对下一个活动的估算。

5. Design in Construction

Design: turning a specification for computer software into operational software

we as software developers shouldn’t try to cram whole programs into our skulls at once; we should try to organize our programs in such a way that we can safely focus on one part of it at a time

5.1. Desirable Characteristics of a Design

  • Minimal complexity
    • Avoid making “clever” designs. Clever designs are usually hard to understand. Instead make “simple” and “easy-to-understand” designs.
  • Ease of maintenance
    • 易维护性意味着为维护程序员设计。不断想象一个维护程序员会对你所写的代码提出什么问题。把维护程序员当作你的听众,然后把系统设计得不言而喻。
  • Loose coupling
    • 可扩展性意味着你可以增强一个系统而不对底层结构造成暴力。你可以改变一个系统的一个部分而不影响其他部分
  • Extensibility
    • 可扩展性意味着你可以增强一个系统而不对底层结构造成暴力。你可以改变一个系统的一个部分而不影响其他部分
  • Reusability
    • 可重用性是指在设计系统时,你可以在其他系统中重用它的碎片。
  • Low-to-medium fan-out
    • 低到中度的扇出意味着让一个特定的类使用低到中度的其他类。高扇出(超过约7个)表示一个类使用了大量的其他类,因此可能过于复杂
    • 扇入 vs 扇出
      • 扇入表示一个模块被多个模块调用 vs 扇出表示一个模块调用多个模块
  • Portability
    • different environment or platforms
  • Leanness
    • 软件的未来版本必须与额外的代码保持后向兼容。致命的问题是:”添加代码很容易,那么我们把它放进去会有什么损失?”
  • Stratification
    • 分层指的是尽量保持分解层次的分层,这样你就可以在任何一个层次上查看系统并得到一致的看法。设计系统时,你可以在一个层次上查看它,而不需要浸入其他层次。
  • Standard techniques.
    • 一个系统越是依赖外来的部件,对于第一次试图理解它的人来说就越是令人生畏。尽量通过使用标准化的、常见的方法给整个系统以熟悉的感觉。
      • 比如python只是用built-in method

        例如,如果你正在编写一个现代系统,而该系统必须使用大量旧的、设计不良的代码,那么就在新系统中编写一个层,负责与旧的代码进行交互。设计这一层,使其隐藏旧代码的不良质量,为较新的层提供一套一致的服务。然后让系统的其他部分使用这些类而不是旧代码。在这种情况下,分层设计的好处是:(1)它把坏代码的混乱分隔开来;(2)如果你被允许抛弃旧代码或重构它,除了接口层之外,你不需要修改任何新代码

5.2. Architectural/ High-level design

Architectural changes are expensive to make during construction or later. The time needed to fix an error in a software architecture is on the same order as that needed to fix a requirements error—that is, more than that needed to fix a coding error

5.2.1. Typical Architectural Components

  1. Program Organization
    1. The architecture should define the major building blocks in a program. Depending on the size of the program, each building block might be a single class or it might be a subsystem consisting of many classes.
  2. Major Classes
    1. he architecture should specify the major classes to be used. It should include descriptions of the class hierarchies, of state transitions, and of object persistence.
  3. Data Design
    1. The architecture should describe the major files and table(类似mysql这种数据表格) designs to be used.
  4. Business Rules
    1. If the architecture depends on specific business rules, it should identify them and describe the impact the rules have on the system’s design 如果架构依赖于特定的业务规则,它应该识别这些规则并描述这些规则对系统设计的影响
  5. User Interface Design
  6. Resource Management
  7. Security
  8. Performance
  9. Scalability
  10. Interoperability 互操作性
  11. Internationalization/Localization 国际化/本地化
  12. Input/Output
  13. Error Processing
  14. Fault Tolerance
  15. Architectural Feasibility 架构可行性
  16. Overengineering
    1. 过度工程化,也称性能过剩、过度设计,是指产品设计的比使用期望有更大的鲁棒性或性能,或者不必要的复杂度
    2. In software, the chain isn’t as strong as its weakest link; it’s as weak as all the weak links multiplied together. The architecture should clearly indicate whether programmers should err on the side of overengineering or on the side of doing the simplest thing that works.
  17. Buy-vs.-Build Decisions
  18. Reuse Decisions
  19. Change Strategy
    1. 产品在整个开发过程中可能会发生变化
  20. General Architectural Quality

5.2.2. Design Challenges

  1. Design Is a Wicked Problem
    1. This paradox implies, essentially, that you have to “solve” the problem once in order to clearly define it and then solve it again to create a solution that works.
    2. 因为在大桥倒塌之前,它的工程师不知道空气动力学需要考虑到这样的程度。只有通过建桥(解决这个问题),他们才能了解到问题中的额外考虑,使他们能够建造另一座仍然屹立不倒的桥
  2. Design Is a Sloppy Process
    1. 完成的软件设计应该看起来井井有条、干净整洁,但用于开发设计的过程却远不如最终结果那样整洁
    2. 设计是马虎的,因为很难知道你的设计什么时候是 “足够好”。多少细节是足够的?
  3. Design Is About Tradeoffs and Priorities
    1. 在一个理想的世界里,每一个系统都可以立即运行,消耗零存储空间,使用零网络带宽,永远不包含任何错误,并且不需要花费任何费用来构建。在现实世界中,设计者工作的一个关键部分是权衡相互竞争的设计特性,并在这些特性之间取得平衡。
  4. Design Involves Restrictions
    1. 设计的意义部分是为了创造可能性,部分是为了限制可能性。如果人们有无限的时间、资源和空间来建造物理结构,你会看到令人难以置信的无边无际的建筑,每只鞋都有一个房间,有数百个房间。这就是软件在没有刻意施加限制的情况下的结果。
  5. Design Is Nondeterministic
    1. 如果你派三个人去设计同一个程序,他们很容易就能带着三种截然不同的设计回来,而每一种都是完全可以接受的。
  6. Design Is a Heuristic Process
    1. Because design is nondeterministic, design techniques tend to be heuristics—“rules of thumb” or “things to try that sometimes work”—rather than repeatable processes that are guaranteed to produce predictable results. Design involves trial and error.
    2. A design tool or technique that worked well on one job or on one aspect of a job might not work as well on the next project. No tool is right for everything.
  7. Design Is Emergent
    1. 设计不会直接从某人的大脑中完全形成。它们通过设计评审、非正式讨论、编写代码的经验以及修改代码的经验来发展和改进。

5.2.3. Design Practices

  • Iterate
    • Design is an iterative process. You don’t usually go from point A only to point B; you go from point A to point B and back to point A
    • you’ll look at both high-level and low-level views
  • Divide
  • Top-Down and Bottom-Up Design Approaches
    • Top down
      • Start with the general problem
      • Break it into manageable parts
      • Each part becomes a new problem
      • Decompose further
      • Level out with concrete code
    • Bottom up
      • Start with a specific capability
      • Implement it
      • Repeat until able to think about higher level pieces
    • Top down and bottom up are not exclusive
  • Collaborative Design
    • 和别人讨论合作
  • Experimental Prototyping
    • 这是提高软件质量的方法之一

5.2.4. Levels of design

0. Software System

  • The first level is the entire system. Some programmers jump right from the system level into designing classes, but it’s usually beneficial to think through higher level combinations of classes, such as subsystems or packages.

1. 划分为子系统(Division into Subsystems or Packages)

在这个层次上特别重要的是关于各个子系统如何通信的规则。如果所有的子系统都能与其他所有的子系统通信,你就失去了将它们分开的好处。通过限制通信,使每个子系统变得有意义。

  • 避免牵一发而动全身

最简单的关系是让一个子系统调用另一个子系统的程序。一个更复杂的关系是让一个子系统包含另一个子系统的类。最复杂的关系是让一个子系统的类继承于另一个子系统的类

一个程序不应该包含任何循环关系,即A类使用B类,B类使用C类,而C类使用A类

2. 划分为类(Division into Classes)

一个数据库接口子系统可能被进一步划分为数据访问类和持久化框架类,以及数据库元数据

3. 划分为例程(Division into Routines)

把一段相对独立的代码写成单独的一个模块就是函数的概念。我们可以在自己的程序中编写很多个函数,从而实现模块化编程。但这些模块或者说函数并不一定向外输出(即提供给别的程序使用),只用于当前这个程序里面。此时这些函数就仅仅具有独立函数的意义,但不是例程

比如DLL里的函数就是例程

先划分外部例程,在划分内部例程

4. Internal routine Design

  • 对象的设计原则(Object design principles)

The steps in designing with objects are

  1. Identify the objects and their attributes (methods and data).
  2. Determine what can be done to each object.
  3. Determine what each object is allowed to do to other objects.
  4. Determine the parts of each object that will be visible to other objects—which parts will be public and which will be private.
  5. Define each object’s public interface.

5.3. Design Building Blocks: Heuristics

5.3.1. 抽象(abstractions)

基类是一种抽象,它允许你专注于一组派生类的共同属性,并在你处理基类的时候忽略具体类的细节。一个好的类的接口是一个抽象,它允许你专注于接口而不需要担心类的内部工作。一个精心设计的例程的接口在较低的细节上提供了同样的好处,而一个精心设计的包或子系统的接口则在较高的细节上提供了这种好处。

从复杂性的角度来看,抽象的主要好处是它允许你忽略不相关的细节。

Good programmers create abstractions at the routine-interface level, class-interface level, and package-interface level—in other words, the doorknob level, door level, and house level—and that supports faster and safer programming.

5.3.2. 封装(Encapsulation)

Encapsulation picks up where abstraction leaves off.(封装是抽象的延续)

  • 抽象说: 你被允许在一个高水平的细节上观察一个对象
  • 封装说: 此外,你不允许在任何其他的细节层次上看一个对象

封装是你可以看房子的外面,但你不能靠近到足以让你看清门的细节。你可以知道有一扇门,也可以知道门是开着还是关着,但你不能知道这扇门是由木头、玻璃纤维、钢还是其他材料制成的。

5.3.3. 继承(Inherit)

继承简化了编程,因为你写一个一般的例程来处理任何依赖于门的一般属性的事情,然后写具体的例程来处理对特定种类的门的具体操作。一些操作,如Open()或Close(),可能适用于无论门是实心门、内门、外门、纱门、法式门还是玻璃滑门。一种语言能够支持像Open()或Close()这样的操作,而在运行时才知道你所处理的是哪种门,这种能力被称为 “多态性”(polymorphism)

5.3.4. 可见性(visibility)

设计一个类的一个关键任务是决定哪些功能应该在该类之外被知道,哪些应该保持秘密。一个类可能会使用25个例程,但只公开其中的5个,在内部使用另外20个。一个类可能会使用几种数据类型,但不公开它们的信息。

  • 养成思考信息隐藏(Information Hiding)的习惯

5.3.5. 确定可能发生变化的领域(Identify Areas Likely to Change)

  • Identify items that seem likely to change
  • Separate items that are likely to change
  • Isolate items that seem likely to change

这体现在

  • 输入与输出
  • 非标准的语言特性(Nonstandard language features.)
    • 语言版本
  • 非标准拓展(nonstandard extensions)

5.3.6. 耦合

耦合度的评判标准(Coupling Criteria)

  1. Size. Size refers to the number of connections between modules 一个需要一个参数的例程比一个需要六个参数的例程与调用它的模块的耦合更松散
  2. Visibility. Visibility refers to the prominence(突出程度) of the connection between two modules 在参数列表中传递数据是一种明显的联系,因此是好的。修改全局数据以便另一个模块可以使用这些数据是一种偷偷摸摸的连接,因此是不好的
  3. Flexibility. Flexibility refers to how easily you can change the connections between modules

耦合类型

  1. Simple-data-parameter coupling
    1. 如果两个模块之间传递的所有数据都是原始数据类型,并且所有数据都是通过参数列表传递,那么这两个模块就是简单数据-参数耦合。
    2. 这种耦合是正常的,可以接受的
  2. Simple-object coupling
    1. 如果一个模块实例化了一个对象,它就是简单对象耦合。
    2. 这种耦合是可接受的
  3. Object-parameter coupling
    1. 如果Object1要求Object2传递给它一个Object3,那么两个模块就是对象-参数耦合的。
    2. 这种耦合比Object1要求Object2只传递原始数据类型更紧密,因为它要求Object2知道Object3
  4. Semantic coupling
    1. 语义耦合。
    2. 最隐蔽的耦合发生在一个模块不是利用另一个模块的某些语法元素,而是利用另一个模块内部工作的某些语义知识
    3. 在静态代码分析工具认为没有耦合的情况之下,如果两个类之间还交换带有隐含意义的数据,假设对方已为自己完成了某种工作,暗示对方执行期望的代码,那么这两个类在语义上还存在着耦合。即“在实现细节上存在依赖,而不是在调用上存在依赖”

对比

  • 允许任意子模块之间相互调用的结果

5.4. key points

  1. Software’s Primary Technical Imperative is managing complexity. This is greatly aided by a design focus on simplicity.
  2. Simplicity is achieved in two general ways: minimizing the amount of essential complexity that anyone’s brain has to deal with at any one time, and keeping accidental complexity from proliferating needlessly.
  3. Design is heuristic. Dogmatic adherence to any single methodology hurts creativity and hurts your programs.
  4. Good design is iterative; the more design possibilities you try, the better your final design will be.
  5. Information hiding is a particularly valuable concept. Asking “What should I hide?” settles many difficult design issues.
  6. Lots of useful, interesting information on design is available outside this book. The perspectives presented here are just the tip of the iceberg.

6. Detailed design

6.1. High-Quality Routines

6.1.1. key points

  1. The most important reason for creating a routine is to improve the intellectual manageability of a program, and you can create a routine for many other good reasons. Saving space is a minor reason; improved readability, reliability, and modifiability are better reasons.
  2. Sometimes the operation that most benefits from being put into a routine of its own is a simple one.
  3. You can classify routines into various kinds of cohesion(内聚性), but you can make most routines functionally cohesive, which is best.
    1. 一般会希望程序的模块有高内聚性,因为高内聚性一般和许多理想的软件特性有关,包括鲁棒性、可靠度、可复用性及易懂性(understandability)等特性,而低内聚性一般也代表不易维护、不易测试、不易复用以及难以理解
  4. The name of a routine is an indication of its quality. If the name is bad and it’s accurate, the routine might be poorly designed.
  5. Functions should be used only when the primary purpose of the function is to return the specific value described by the function’s name.
  6. Careful programmers use macro routines with care and only as a last resort.

6.2. Defensive Programming

In defensive programming, the main idea is that if a routine is passed bad data, it won’t be hurt, even if the bad data is another routine’s fault. More generally, it’s the recognition that programs will have problems and modifications, and that a smart programmer will develop code accordingly.

在防御性编程中,其主要思想是,如果一个程序被传递了坏数据,它不会受到伤害,即使坏数据是另一个程序的错误。更广泛地说,它是一种认识,即程序会有问题和修改,聪明的程序员会相应地开发代码。

One of the paradoxes of defensive programming is that during development, you’d like an error to be noticeable—you’d rather have it be obnoxious than risk overlooking it. But during production, you’d rather have the error be as unobtrusive as possible, to have the program recover or fail gracefully.
防御性编程的一个悖论是,在开发过程中,你希望错误是明显的–你宁愿它是令人讨厌的,而不是冒着被忽视的风险。但在生产过程中,你宁愿让错误尽可能的不显眼,让程序恢复或优雅地失败

For production software, garbage in, garbage out isn’t good enough. A good program never puts out garbage, regardless of what it takes in. A good program uses “garbage in, nothing out,” “garbage in, error message out,” or “no garbage allowed in” instead. By today’s standards, “garbage in, garbage out” is the mark of a sloppy, nonsecure program.

  • Protecting Your Program from Invalid Inputs
  • Assertions

6.3. Assertions 断言

  • An assertion is code that’s used during development—usually a routine or macro(宏)—that allows a program to check itself as it runs
  • when an assertion is true, that means everything is operating as expected.
  • When it’s false, that means it has detected an unexpected error in the code.

handle garbarge in:

  • Check the values of all data from external sources

    • Make sure that numeric values are within tolerances and that strings are short enough to handle.
  • Check the values of all routine input parameters

  • Decide how to handle bad inputs

  • handle garbarge in:

  • Check the values of all data from external sources

    • Make sure that numeric values are within tolerances and that strings are short enough to handle.
  • Check the values of all routine input parameters

  • Decide how to handle bad inputs

一个断言通常需要两个参数:一个描述假设的布尔表达式,它应该是真实的,以及一个在它不真实时显示的信息。

During production, they can be compiled out of the code so that the assertions don’t degrade system performance

  • Use error-handling code for conditions you expect to occur
  • use assertions for conditions that shouldnever occur Assertions check for conditions that should never occur.

使用断言来记录和验证先决条件(precondition)和后决条件(postcondition)。先决条件和后决条件是程序设计和开发方法的一部分,被称为 “契约式设计”(Meyer 1997)。当使用前置条件和后置条件时,每个例程或类与程序的其他部分形成一个契约

6.3.1. key points

  1. Production code should handle errors in a more sophisticated way than “garbage in, garbage out.”
  2. Defensive-programming techniques make errors easier to find, easier to fix, and less damaging to production code.
  3. Assertions can help detect errors early, especially in large systems, high-reliability systems, and fast-changing code bases.
  4. The decision about how to handle bad inputs is a key error-handling decision and a key high-level design decision.
  5. Exceptions provide a means of handling errors that operates in a different dimension from the normal flow of the code. They are a valuable addition to the programmer’s intellectual toolbox when used with care, and they should be weighed against other error-processing techniques.
  6. Constraints that apply to the production system do not necessarily apply to the development version. You can use that to your advantage, adding code to the development version that helps to flush out errors quickly.

6.4. 伪代码编程过程(Pseudocode Programming Process,PPP)

The term “pseudocode” refers to an informal, English-like notation for describing how an algorithm, a routine, a class, or a program will work

术语 “伪代码 “是指一种非正式的、类似英语的符号,用于描述算法、例程、类或程序如何工作。

  • Pseudocode makes reviews easier.
    • You can review detailed designs without examining source code
  • Pseudocode supports the idea of iterative refinement.
    • You start with a high-level design, refine the design to pseudocode, and then refine the pseudocode to source code.
  • Pseudocode makes changes easier
    • A few lines of pseudocode are easier to change than a page of code
  • Pseudocode minimizes commenting effort
  • Pseudocode is easier to maintain than other forms of design documentation

6.5. Collaborative development

  • The primary purpose of collaborative construction is to improve software quality.
  • Various studies have shown that in addition to being more effective at catching errors than testing, collaborative practices find different kinds of errors than testing does
    • 比如inadequate comments, hard-coded variable values

6.5.1. pair programming

When pair programming, one programmer types in code at the keyboard and the other programmer watches for mistakes and thinks strategically about whether the code is being written correctly and whether the right code is being writte

  • 不要让结对编程变成观察
  • 不要强迫对容易的东西进行配对编程
  • 定期轮换配对和工作任务
  • 鼓励配对者配合对方的进度

benefit

  • It holds up better under stress than solo development.
  • It improves code quality. The readability and understandability of the code tends to rise to the level of the best programmer on the team.
  • It shortens schedules. Pairs tend to write code faster and with fewer errors.
  • It produces all the other general benefits of collaborative construction, including disseminating corporate culture, mentoring junior programmers, and fostering collective ownership.

6.5.2. Formal Inspections

An inspection is a specific kind of review that has been shown to be extremely effective in detecting defects and to be relatively economical compared to testing.

  • Checklists focus the reviewers’ attention on areas that have been problems in the past.
  • The inspection focuses on defect detection, not correction.
  • Reviewers prepare for the inspection meeting beforehand and arrive with a list of the problems they’ve discovered.

role

  • Author
  • Reviewer
  • Management
  • Scribe

General Procedure

  • Planning.
    • The author gives the design or code to the moderator. The moderator decides who will review the material and when and where the inspection meeting will occur; the moderator then distributes the design or code and a checklist that focuses the attention of the inspectors. Materials should be printed with line numbers to speed up error identification during the meeting.
  • Overview.
    • When the reviewers aren’t familiar with the project they are reviewing, the author can spend up to an hour or so describing the technical environment within which the design or code has been created. Having an overview tends to be a dangerous practice because it can lead to a glossing over of unclear points in the design or code under inspection. The design or code should speak for itself; the overview shouldn’t speak for it.
  • Preparation.
    • Each reviewer works alone to scrutinize the design or code for errors. The reviewers use the checklist to stimulate and direct their examination of the review materials.

6.5.3. Walk-Throughs

1
2
3
4
5
6
7
8
9
10
11
12
13
它可能是非正式的,就像围绕着白板的即兴公牛会议一样。

演练通常由被审查的设计或代码的作者主持和调节。

演练的重点是技术问题,这是一个工作会议。

所有的参与者通过阅读设计或代码并寻找错误来为演练做准备。

走过场是高级程序员向初级程序员传授经验和企业文化的一个机会。这也是初级程序员展示新方法的机会,也是挑战陈旧的、可能已经过时的假设的机会。

走访通常持续30至60分钟。

重点是错误检测,而不是纠正。

6.5.4. key point

  1. Collaborative development practices tend to find a higher percentage of defects than testing and to find them more efficiently.
  2. Collaborative development practices tend to find different kinds of errors than testing does, implying that you need to use both reviews and testing to ensure the quality of your software.
  3. Formal inspections use checklists, preparation, well-defined roles, and continual process improvement to maximize error-detection efficiency. They tend to find more defects than walk-throughs.
  4. Pair programming typically costs about the same as inspections and produces similar quality code. Pair programming is especially valuable when schedule reduction is desired.
  5. Formal inspections can be used on work products such as requirements, designs, and test cases, as well as on code.
  6. Walk-throughs and code reading are alternatives to inspections. Code reading offers more flexibility in using each person’s time effectively.

7. Debugging

A defect in a software system is a quality level (for some quality) that is not acceptable.

Defectiveness is relative to a specification. if the specification requires the software to crash, then crashing in that circumstances isn’t a bug.

Debugging is the process of identifying the root cause of an error and correcting it

7.1. The Scientific Method of Debugging

  1. Gather data through repeatable experiments.
  2. Form a hypothesis that accounts for the relevant data.
  3. Design an experiment to prove or disprove the hypothesis.
  4. Prove or disprove the hypothesis.
  5. Repeat as needed.

The hard part of debugging is finding the defect. Fixing the defect is the easy part. But as with many eaasy tasks, the fact that it’s easy makes it especially error-prone. At least one study found that defect corrections have more than a 50 percent chance of being wrong the first time

7.2. key points

  1. Debugging is a make-or-break aspect of software development. The best approach is to use other techniques described in this book to avoid defects in the first place. It’s still worth your time to improve your debugging skills, however, because the difference between good and poor debugging performance is at least 10 to 1.
  2. A systematic approach to finding and fixing errors is critical to success. Focus your debugging so that each test moves you a step forward. Use the Scientific Method of Debugging.
  3. Understand the root problem before you fix the program. Random guesses about the sources of errors and random corrections will leave the program in worse condition than when you started.
  4. Set your compiler warning to the pickiest level possible, and fix the errors it reports. It’s hard to fix subtle errors if you ignore the obvious ones.
  5. Debugging tools are powerful aids to software development. Find them and use them, and remember to use your brain at the same time.

7.3. Debug Cycle

Input: An indication of a defect

  1. Stabilise — Make reliably repeatable(可复现)
  2. Isolate (or localise) — To the smallest unit
  3. Explain — What’s wrong with the code
  4. Repair — Replace the broken code
  5. Test — Verify the fix
    Check for
  • Regressions
  • Masked bugs
  • Nearby bugs

An indication of a defect is a tangible(有形的) record of a behaviour contrary to the (explicit or implicit) functional specification in a designated situation

缺陷的迹象是在指定情况下违反(明确或隐含的)功能规范的行为的具体记录

Stabilise

  • Bugs are often very situation dependent
    • Precise input + state
      • OS, hardware
      • Sequence of actions
      • Length of operating
  • A stabilised bug
    • is reliably repeatable
    • preferably with minimal sufficient conditions

Isolate(Localise)

  • 隔离并非是字面意思,例如bug可能出现在Communication points。
  • A defect is isolated if
    • you have identified the minimum subsystem necessary to exhibit the defect
    • for an trigger input and situation

Test

  • Post fix
    • You need to verify
      • Your theory
      • Your *execution of the fix
    • You need to guard against
      • Unintended consequences!
  • “New” bugs arise
    • Bugs in the fix
      • The fix is incomplete
      • The fix triggers a regression
    • Masked bugs

7.4. Post Successful Fix

7.5. Nearby Bugs

Bugs come in families

  • Similar mistakes
    • Persistent misunderstanding with multiple manifestations
  • Clustered mistakes
    • Some bugs hidden
    • Some routines are broken
  • A bug is a predictor of more bugs

7.6. WONTFIX

  • The bug is too small
    • Or insignificant
    • Or ambiguous
  • The bug is too big
    • It would change too much behavior which some people rely on
  • The bug is too hard

8. Developer Testing

Software testing is a procedure, in which every one of the stages like test planning, test development, test execution, result investigation, tracking of bug and reporting are accomplished effectively.

  • Unit testing is the execution of a complete class, routine, or small program that has been written by a single programmer or team of programmers, which is tested in isolation from the more complete system.
    • 单元测试是对一个完整的类、例程或小程序的执行,这些程序是由一个程序员或程序员团队编写的,它与更完整的系统隔离测试。
  • Component testing is the execution of a class, package, small program, or other program element that involves the work of multiple programmers or programming teams, which is tested in isolation from the more complete system.
    • 组件测试是指执行一个类、包、小程序或其他涉及多个程序员或编程团队工作的程序元素,它与更完整的系统隔离测试。
  • Integration testing is the combined execution of two or more classes, packages, components, or subsystems that have been created by multiple programmers or programming teams. This kind of testing typically starts as soon as there are two classes to test and continues until the entire system is complete.
    • 集成测试是对由多个程序员或编程团队创建的两个或多个类、包、组件或子系统的联合执行。这种测试通常在有两个类需要测试时就开始,一直持续到整个系统完成。
  • Regression testing is the repetition of previously executed test cases for the purpose of finding defects in software that previously passed the same set of tests.
    • 回归测试是重复以前执行的测试用例,目的是发现以前通过同一组测试的软件中的缺陷。
  • System testing is the execution of the software in its final configuration, including integration with other software and hardware systems. It tests for security, performance, resource loss, timing problems, and other issues that can’t be tested at lower levels of integration.
    • 系统测试是在其最终配置中执行软件,包括与其他软件和硬件系统的集成。它测试安全、性能、资源损失、时间问题和其他无法在较低的集成水平上测试的问题

8.1. Role of Developer Testing in Software Quality

  • 测试是任何软件质量计划的一个重要部分,而且在很多情况下,它是唯一的部分——这是不幸的,因为各种形式的合作开发实践(collaborative development practices)已经被证明比测试能发现更多的错误,而且他们发现每个错误的成本还不到测试的一半
  • Testing by itself does not improve software quality. Test results are an indicator of quality, but in and of themselves they don’t improve it. Trying to improve software quality by increasing the amount of testing is like trying to lose weight by weighing yourself more often.
    • 测试本身并不能提高软件质量。测试结果是质量的一个指标,但其本身并不能提高质量。试图通过增加测试量来提高软件质量,就像试图通过增加体重来减轻体重。
  • 你必须希望能在你的代码中发现错误,这样的希望似乎是一种不自然的行为
  • 但测试会描述软件的可靠程度reliability,并且测试可以而且通常会指导对软件的修正do guide corrections to the software

8.2. Approach to Developer Testing

  1. Test for each relevant requirement to make sure that the requirements have been implemented.
  2. Test for each relevant design concern to make sure that the design has been implemented.
  3. Use “basis testing“ to add detailed test cases to those that test the requirements and the design
  4. Use a checklist of the kinds of errors you’ve made on the project to date or have made on previous projects.

8.3. Test first rather than last

  • writing test cases first will minimize the amount of time between when a defect is inserted into the code and when the defect is detected and removed
    • 先写测试用例,可以最大限度地减少从缺陷被插入代码到缺陷被发现和消除的时间。
  • 在写代码之前写测试用例并不比在写代码之后写测试用例花费更多的精力;它只是重新排列了测试用例的编写活动
  • When you write test cases first, you detect defects earlier and you can correct them more easily.
    • 当你先写测试用例时,你会更早地发现缺陷,并能更容易地纠正它们。
  • 先写测试用例迫使你在写代码之前至少考虑一下需求和设计,这往往会产生更好的代码
  • Writing test cases first exposes requirements problems sooner, before the code is written, because it’s hard to write a test case for a poor requirement.
    • 在写代码之前,先写测试用例会更早地暴露需求问题,因为很难为一个糟糕的需求写一个测试用例

8.4. Limitations of Developer Testing

注意讨论的是developer写的test

  1. Developer tests tend to be “clean tests“.Developers tend to test for whether the code works (clean tests) rather than test for all the ways the code breaks (dirty tests).
  2. Developer testing tends to have an optimistic view of test coverage
  3. Developer testing tends to skip more sophisticated(复杂的) kinds of test coverage

8.5. Testing Trick

  • Incomplete Testing
    • 你需要集中精力挑选几个能告诉你不同事情的测试用例,而不是一组重复告诉你同样的事情。
  • Structured Basis Testing
    • 类似”code coverage” testing or “logic coverage” testing
  • Data-Flow Testing
    • 关注数据定义、使用以及引用等场合
    • Defined Used Killed Referred
  • Equivalence Partitioning
    • 一个好的测试用例涵盖了很大一部分可能的输入数据。如果两个测试用例冲出完全相同的错误,你只需要其中一个。等价分割 “的概念是这个想法的形式化,有助于减少所需的测试用例的数量。
  • Errors in Testing Itself

8.6. 面向测试设计(Design for Test)

如果你设计的系统便于测试,那么这个系统会是什么样子。你是否需要将用户界面与代码的其他部分分开,以便你可以独立地进行测试?你是否需要组织每个子系统,使其尽量减少对其他子系统的依赖性?为测试而设计往往会导致更正式的类接口(formalized class interfaces),这通常是有益的。

1
2
3
4
5
6
7
8
9
def get_file_list():
# Get list of arguments from the command line, minus "wc.py"
args_list = sys.argv[1:]
...

# Fixed
def get_file_list(args):
# Get list of arguments from the command line, minus "wc.py"
args_list = args[1:]

8.6.1. code coverage(line coverage) vs inputs coverage vs logic coverage

  • 代码覆盖是指覆盖所有计算与判断语句(不包括赋值和retrun等)
  • 代码覆盖只是对标代码行数,而输入覆盖是指覆盖所有潜在输入。
  • 输入覆盖应当基于不了解函数前置限定的情况下进行,例如某一函数的前置条件是输入不为0,但实际测试时是无视这一点的(就好比直接套通用测试用例集)
  • logic coverage
    • 有限状态机finite state machine

8.6.2. failing test

  1. 如果测试是正确的,那么说明软件系统(包括测试部分)有缺陷(defect)。注意:并不一定是测试本身触发了缺陷,可能是测试文件的文件名、测试的初始设置上的问题导致触发的缺陷。
  2. 如果测试是错误的,那么说明测试有缺陷。

8.7. Pain Points

A part of the system that recurrently(反复) causes problems

8.8. 5W+H

测试要基于5W+H来思考与设计

  1. What
    1. 我们正在利用什么技术进行软件测试开发?
    2. 这个开发项目一开始考虑的是什么技术?
    3. 对客户的现有系统有什么了解?
  2. Why
    1. 为什么这个问题在软件测试或项目开始之前或接近开始时没有被发现?
    2. 为什么技术不能变好?
  3. When
    1. 最初发现软件测试的问题或需求是什么时候?
    2. 何时对相似性问题进行反思和检查?
  4. Who
    1. 谁负责计划和变更管理的批准?
    2. 谁负责保证项目组内技术的一致性?
    3. 谁负责与客户对接
  5. Where
    1. 软件测试的质量确认在哪里找到?
    2. 关于框架相似性的文件保存在哪里?
  6. How
    1. 问题是如何发生的?
    2. 一连串的场合是如何促使问题被发现的?

9. Refactoring 重构

9.1. Philosophy of Software Evolution

The Cardinal Rule of Software Evolution is that evolution should improve the internal quality of the program.

  • 总的来说还是为了提高软件质量

9.2. definition

In computer programming and software design, code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior

  • 不改变:功能
  • 改变:可测试性、可读性、可理解性、可重用性

9.3. Reasons to Refactor

为何重构

  • 对问题理解的加深
  • code is duplicate
  • A routine is too long
    • 在面向对象的编程中,很少需要超过一个屏幕的例程
  • A class has poor cohesion.
  • A loop is too long or too deeply nested
  • A parameter list has too many parameters
  • …

9.4. Reasons Not to Refactor

  • Change in itself is not a virtue
  • Avoid refactoring instead of rewriting.
    • Sometimes code doesn’t need small changes—it needs to be tossed out so that you can start over.
  • Don’t use refactoring as a cover for code and fix
    • 是提高quality而不是correctness

9.5. Specific Refactoring

  1. Data-Level Refactorings
    1. Rename a variable with a clearer or more informative name.
  2. Statement-Level Refactorings
    1. Move a complex boolean expression into a well-named boolean function.
  3. Routine-Level Refactorings
    1. Convert a long routine to a class.
  4. Class Implementation Refactorings
    1. Change value objects to reference objects.
  5. Class Interface Refactorings
    1. Convert one class to two.
  6. System-Level Refactorings
    1. Create a definitive reference source for data you can’t control

9.6. Refactoring Safely

  • Save the code you start with.
  • Keep refactorings small.
  • Do refactorings one at a time.
  • Make a list of steps you intend to take.
  • Review the changes

Small changes tend to be more error-prone than larger changes

9.7. Refactoring Strategies

  • Refactor when you add a routine
    • 当你添加一个例程时,检查相关的例程是否组织良好。如果不是,就重构它们
  • Refactor when you add a class
  • Refactor when you fix a defect
    • 利用你从修复一个错误中获得的理解,来改进其他可能容易出现类似缺陷的代码
  • Target error-prone modules瞄准容易出错的模块
  • Target high-complexity modules.

One strategy for improving production code is to refactor poorly written legacy code as you touch it

10. Code-Tuning Strategies

  • Code tuning is one way of improving a program’s performance
    • chosen efficiency as a priority 效率(速度)优先
  • Think about efficiency from each of these viewpoints
    • Program requirements
    • Program design
    • Class and routine design
    • Operating-system interactions
    • Code compilation
    • Hardware
    • Code tuning

The Pareto Principle

  • 帕累托原则,也被称为80/20规则,指出你可以用20%的努力获得80%的结果。该原则适用于编程以外的很多领域,但它绝对适用于程序优化 you can get 80 percent of the result with 20 percent of the effort
  • a program’s routines consume 80 percent of its execution time
    • 这是我们优化的目标

10.1. When to Tune

  • Don’t optimize until you know you need to

10.2. key point

  1. Performance is only one aspect of overall software quality, and it’s usually not the most important. Finely tuned code is only one aspect of overall performance, and it’s usually not the most significant. Program architecture, detailed design, and data-structure and algorithm selection usually have more influence on a program’s execution speed and size than the efficiency of its code does.
  2. Quantitative measurement is a key to maximizing performance.
  3. Most programs spend most of their time in a small fraction of their code. You won’t know which code that is until you measure it.
  4. Multiple iterations are usually needed to achieve desired performance improvements through code tuning.
  5. The best way to prepare for performance work during initial coding is to write clean code that’s easy to understand and modify.
  • Software Engineering
西行漫记-其四
密码学备忘录-数学基础
  1. 1. 1. Ideal Software Construction 理想的软件建构步骤
  2. 2. 2. algorithm vs heuristic(启发)
    1. 2.1. 2.1. metaphor(隐喻)
    2. 2.2. 2.2. 水性沉淀物的隐喻
    3. 2.3. 2.3. incremental development 增量开发-骨架构建
  3. 3. 3. Problem-Definition
  4. 4. 4. Requirements development
    1. 4.1. 4.1. Estimating a Construction Schedule
  5. 5. 5. Design in Construction
    1. 5.1. 5.1. Desirable Characteristics of a Design
    2. 5.2. 5.2. Architectural/ High-level design
      1. 5.2.1. 5.2.1. Typical Architectural Components
      2. 5.2.2. 5.2.2. Design Challenges
      3. 5.2.3. 5.2.3. Design Practices
      4. 5.2.4. 5.2.4. Levels of design
    3. 5.3. 5.3. Design Building Blocks: Heuristics
      1. 5.3.1. 5.3.1. 抽象(abstractions)
      2. 5.3.2. 5.3.2. 封装(Encapsulation)
      3. 5.3.3. 5.3.3. 继承(Inherit)
      4. 5.3.4. 5.3.4. 可见性(visibility)
      5. 5.3.5. 5.3.5. 确定可能发生变化的领域(Identify Areas Likely to Change)
      6. 5.3.6. 5.3.6. 耦合
    4. 5.4. 5.4. key points
  6. 6. 6. Detailed design
    1. 6.1. 6.1. High-Quality Routines
      1. 6.1.1. 6.1.1. key points
    2. 6.2. 6.2. Defensive Programming
    3. 6.3. 6.3. Assertions 断言
      1. 6.3.1. 6.3.1. key points
    4. 6.4. 6.4. 伪代码编程过程(Pseudocode Programming Process,PPP)
    5. 6.5. 6.5. Collaborative development
      1. 6.5.1. 6.5.1. pair programming
      2. 6.5.2. 6.5.2. Formal Inspections
      3. 6.5.3. 6.5.3. Walk-Throughs
      4. 6.5.4. 6.5.4. key point
  7. 7. 7. Debugging
    1. 7.1. 7.1. The Scientific Method of Debugging
    2. 7.2. 7.2. key points
    3. 7.3. 7.3. Debug Cycle
    4. 7.4. 7.4. Post Successful Fix
    5. 7.5. 7.5. Nearby Bugs
    6. 7.6. 7.6. WONTFIX
  8. 8. 8. Developer Testing
    1. 8.1. 8.1. Role of Developer Testing in Software Quality
    2. 8.2. 8.2. Approach to Developer Testing
    3. 8.3. 8.3. Test first rather than last
    4. 8.4. 8.4. Limitations of Developer Testing
    5. 8.5. 8.5. Testing Trick
    6. 8.6. 8.6. 面向测试设计(Design for Test)
      1. 8.6.1. 8.6.1. code coverage(line coverage) vs inputs coverage vs logic coverage
      2. 8.6.2. 8.6.2. failing test
    7. 8.7. 8.7. Pain Points
    8. 8.8. 8.8. 5W+H
  9. 9. 9. Refactoring 重构
    1. 9.1. 9.1. Philosophy of Software Evolution
    2. 9.2. 9.2. definition
    3. 9.3. 9.3. Reasons to Refactor
    4. 9.4. 9.4. Reasons Not to Refactor
    5. 9.5. 9.5. Specific Refactoring
    6. 9.6. 9.6. Refactoring Safely
    7. 9.7. 9.7. Refactoring Strategies
  10. 10. 10. Code-Tuning Strategies
    1. 10.1. 10.1. When to Tune
    2. 10.2. 10.2. key point
© 2024 何决云 载入天数...