6.031 Software Construction 笔记

279 阅读6分钟

6.031是一门软件工程专业课

优点:教材和CS161计算机安全一样采用的网页文档,感觉读起来比啃书更容易接受。内容短小精炼,直接过一遍就有不错的收获。课程基于Java,作为读者的感觉像是为已经入门了Java或者任意一门编程语言,想要参与到软件工程开发中的同学们准备的,覆盖了一些编程的范式、设计、调试、合作的原则,确实和实际工作联系比较紧密,值得一读。

缺点:部分章节特别是后半部分章节感觉有点脱离入门工程的初衷,在讲一些语言入门或者计算机架构本身的内容,和其他课程已有知识耦合度过高,增量信息少,可以选择性跳过。

课程网址 web.mit.edu/6.031/www/s…

这篇笔记简要摘录一些相对其他课程的增量信息。

Reading 3: Testing

软件工程第一点不同就是凸显了测试的作用,这是和其他专业课最大的不同之一。一个工程项目要可靠,单测和集成测试必不可少,测试代码是实际开发的一大组成部分。

Unit tests test a single module in isolation. An integration test tests a combination of modules, or even the entire program. Running all your tests after every change is called regression testing. Automated regression testing is a best-practice of modern software engineering.

Reading 4: Code Review

Code review really has two purposes:

Improving the code. Finding bugs, anticipating possible bugs, checking the clarity of the code, and checking for consistency with the project’s style standards.

Improving the programmer. Code review is an important way that programmers learn and teach each other, about new language features, changes in the design of the project or its coding standards, and new techniques. In open source projects, particularly, much conversation happens in the context of code reviews.

Code styles:

  1. Don’t repeat yourself (DRY)
  2. Comments where needed
  3. Fail fast. Failing fast means that code should reveal its bugs as early as possible. The earlier a problem is observed (the closer to its cause), the easier it is to find and fix.
  4. Avoid magic numbers
  5. One purpose for each variable
  6. Use good names
  7. Don’t use global variables
  8. Methods should return results, not print them
  9. Avoid unnecessary special-case code

Reading 5: Version Control

Git. 市面上有海量好教程了,看官网也很推荐。command常用的就那么几个,况且IDE可以点点点,提两次PR,解决两次版本conflict也就明白了。

Reading 6: Specifications

Abstractly speaking, a specification of a method has several parts:

  1. a method signature, giving the name, parameter types, return type, and exceptions thrown
  2. a requires clause, describing additional restrictions on the parameters
  3. an effects clause, describing the return value, exceptions, and other effects of the method

截屏2024-02-27 23.07.07.png

The precondition is an obligation on the client (the caller of the method). It is a condition over the state in which the method is invoked. One aspect of the precondition is the number and types of the parameters in the method signature. Additional conditions are written down in the requires clause

The postcondition is an obligation on the implementer of the method. It includes the parts that Java can statically check: the return type and declared checked exceptions. Additional conditions are written down in the effects clause

A good specification clearly documents the mutual assumptions that a client and implementer are relying on. Bugs often come from disagreements at the interfaces, and the presence of a specification reduces that. A short, simple spec is easier to understand than the implementation itself, and saves other people from having to read the code.

Reading 7: Designing Specifications

three dimensions for comparing specs:

  1. How deterministic it is. Does the spec define only a single possible output for a given input, or does it allow the implementor to choose from a set of legal outputs?
  2. How declarative it is. Does the spec just characterize what the output should be, or does it explicitly say how to compute the output?
  3. How strong it is. Does the spec have a small set of legal implementations, or a large set?

A specification S2 is stronger than or equal to a specification S1 if and only if

  1. S2’s precondition is weaker than or equal to S1’s,
  2. S2’s postcondition is stronger than or equal to S1’s, for the states that satisfy S1’s precondition.

If this is the case, then an implementation that satisfies S2 can be used to satisfy S1 as well, and it’s safe to replace S1 with S2 in your program.

This rule embodies several ideas. It tells you that you can always weaken the precondition, because placing fewer demands on a client will never upset them. And you can always strengthen the postcondition, which means making more promises to the client.

Reading 8: Mutability & Immutability

能用immutable就不要用变量:

  1. 变量是绝大多数bug的来源。特别是存在alias关联复杂的情况下,你的修改可能带来意想不到的bug
  2. 不可变量更容易理解,读代码或者debug的时候负担小很多
  3. 不可变量不会被意外篡改,稳定性高,在合作开发时可以使已有功能不受或少受新修改的影响

简洁原则:能不变就不要可变,能private就不要public,能局部就不要全局。限制不必要的信息和修改的传播

Reading 9: Avoiding Debugging

First defense: make bugs impossible:

  1. static checking by compiler + dynamic checking by runtime
  2. immutability

Second defense: localize bugs

If we can’t prevent bugs, we can try to localize them to a small part of the program, so that we don’t have to look too hard to find the cause of a bug.

Fail fast + defensive programming(such as Checking preconditions)

Assertions

A serious problem with Java assertions is that assertions are off by default. So you have to enable assertions explicitly by passing -ea (which stands for enable assertions) to the Java virtual machine.

Here are some things you should assert:

  1. Method argument requirements.
  2. Method return value requirements. This kind of assertion is sometimes called a self check.

Runtime assertions are not free. They can clutter the code, so they must be used judiciously. Avoid trivial assertions, just as you would avoid uninformative comments.

Incremental development

A great way to localize bugs to a tiny part of the program is incremental development. Build only a bit of your program at a time, and test that bit thoroughly before you move on. That way, when you discover a bug, it’s more likely to be in the part that you just wrote, rather than anywhere in a huge pile of code.

Our class on testing talked about two techniques that help with this:

  1. Unit testing: when you test a module in isolation, you can be confident that any bug you find is in that unit – or maybe in the test cases themselves.
  2. Regression testing: when you’re adding a new feature to a big system, run the regression test suite as often as possible. If a test fails, the bug is probably in the code you just changed.

Reading 13: Debugging

Reproduce the bug and find the bug using the scientific method:

  1. Study the data. Look at the test input that causes the bug, and examine the incorrect results, failed assertions, and stack traces that result from it.
  2. Hypothesize. Propose a hypothesis, consistent with all the data, about where the bug might be, or where it cannot be. It’s good to make this hypothesis general at first.
  3. Experiment. Devise and run an experiment that tests your hypothesis. It’s good to make the experiment an observation at first – a probe that collects information but disturbs the system as little as possible.
  4. Repeat. Add the data you collected from your experiment to what you knew before, and make a fresh hypothesis. Hopefully you have ruled out some possibilities and narrowed the set of possible locations and reasons for the bug.

Delta debugging: compare successful runs with failing runs to try to localize the bug.

Experiment: print intermediate results out; add extra log; add assertion of internal states or variable values; breakpoint and expression calculator (preferred if available)

Some tips:

  • One bug at a time
  • Get a fresh view
  • Sleep on it

Reading 14: Recursion

递归是分解复杂问题的利器,找到base case和recursive step即可解决问题

递归的应用场景有两类:问题本质是递归的(fib数列);数据结构是递归的(file system)

Here are three common ways that a recursive implementation can go wrong:

  1. The base case is missing entirely, or the problem needs more than one base case but not all the base cases are covered.
  2. The recursive step doesn’t reduce to a smaller subproblem, so the recursion doesn’t converge.
  3. Aliases to a mutable data structure are inadvertently shared, and mutated, among the recursive calls.

Look for these when you’re debugging.

On the bright side, what would be an infinite loop in an iterative implementation usually becomes a StackOverflowError in a recursive implementation. A buggy recursive program sometimes fails faster.