diff options
Diffstat (limited to 'cpp/002trace')
-rw-r--r-- | cpp/002trace | 167 |
1 files changed, 83 insertions, 84 deletions
diff --git a/cpp/002trace b/cpp/002trace index b4d0cfca..dd07beed 100644 --- a/cpp/002trace +++ b/cpp/002trace @@ -1,87 +1,86 @@ -// The goal of this skeleton is to make programs more easy to understand and -// more malleable, easy to rewrite in radical ways without accidentally -// breaking some corner case. Tests further both goals. They help -// understandability by letting one make small changes and get feedback. What -// if I wrote this line like so? What if I removed this function call, is it -// really necessary? Just try it, see if the tests pass. Want to explore -// rewriting this bit in this way? Tests put many refactorings on a firmer -// footing. -// -// But the usual way we write tests seems incomplete. Refactorings tend to -// work in the small, but don't help with changes to function boundaries. If -// you want to extract a new function you have to manually test-drive it to -// create tests for it. If you want to inline a function its tests are no -// longer valid. In both cases you end up having to reorganize code as well as -// tests, an error-prone activity. -// -// This file tries to fix this problem by supporting domain-driven testing -// rather than coverage-driven testing. The goal isn't to test all possible -// paths in the code any longer, but to focus on the domain of inputs the -// program should work on. All tests invoke the program in a single way: by -// calling run() with different inputs. The program operates on the input and -// logs _facts_ it deduces to a trace: -// trace("label") << "fact 1: " << val; -// -// The tests check for facts: -// :(scenario foo) -// 34 # call run() with this input -// +label: fact 1: 34 # trace should have logged this at the end -// -label: fact 1: 35 # trace should never contain such a line -// -// Since we never call anything but the run() function directly, we never have -// to rewrite the tests when we reorganize the internals of the program. We -// just have to make sure our rewrite deduces the same facts about the domain, -// and that's something we're going to have to do anyway. -// -// To avoid the combinatorial explosion of integration tests, we organize the -// program into different layers, and each fact is logged to the trace with a -// specific label. Individual tests can focus on specific labels. In essence, -// validating the facts logged with a specific label is identical to calling -// some internal subsystem. -// -// Traces interact salubriously with layers. Thanks to our ordering -// directives, each layer can contain its own tests. They may rely on other -// layers, but when a test fails its usually due to breakage in the same -// layer. When multiple tests fail, it's usually useful to debug the very -// first test to fail. This is in contrast with the traditional approach, -// where changes can cause breakages in faraway subsystems, and picking the -// right test to debug can be an important skill to pick up. -// -// A final wrinkle is for recursive functions; it's often useful to segment -// calls of different depth in the trace: -// +eval/1: => 34 # the topmost call to eval should have logged this line -// (look at new_trace_frame below) -// -// To build robust tests, trace facts about your domain rather than details of -// how you computed them. -// -// More details: http://akkartik.name/blog/tracing-tests -// -// --- -// -// Between layers and domain-driven testing, programming starts to look like a -// fundamentally different activity. Instead of a) superficial, b) local rules -// on c) code [like http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet], -// we allow programmers to engage with the a) deep, b) global structure of the -// c) domain. If you can systematically track discontinuities in the domain -// you don't care if the code used gotos as long as it passed the tests. If -// tests become more robust to run it becomes easier to try out radically -// different implementations for the same program. If code is super-easy to -// rewrite, it becomes less important what indentation style it uses, or that -// the objects are appropriately encapsulated, or that the functions are -// referentially transparent. -// -// Instead of plumbing, programming becomes building and gradually refining a -// map of the environment the program must operate under. Whether a program is -// 'correct' at a given point in time is a red herring; what matters is -// avoiding regression by monotonically nailing down the more 'eventful' parts -// of the terrain. It helps readers new and old and rewards curiosity to -// organize large programs in self-similar hiearchies of example scenarios -// colocated with the code that makes them work. -// -// "Programming properly should be regarded as an activity by which -// programmers form a mental model, rather than as production of a program." -// -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22) +//: The goal of this skeleton is to make programs more easy to understand and +//: more malleable, easy to rewrite in radical ways without accidentally +//: breaking some corner case. Tests further both goals. They help +//: understandability by letting one make small changes and get feedback. What +//: if I wrote this line like so? What if I removed this function call, is it +//: really necessary? Just try it, see if the tests pass. Want to explore +//: rewriting this bit in this way? Tests put many refactorings on a firmer +//: footing. +//: +//: But the usual way we write tests seems incomplete. Refactorings tend to +//: work in the small, but don't help with changes to function boundaries. If +//: you want to extract a new function you have to manually test-drive it to +//: create tests for it. If you want to inline a function its tests are no +//: longer valid. In both cases you end up having to reorganize code as well as +//: tests, an error-prone activity. +//: +//: This file tries to fix this problem by supporting domain-driven testing +//: We try to focus on the domain of inputs the program should work on. All +//: tests invoke the program in a single way: by calling run() with different +//: inputs. The program operates on the input and logs _facts_ it deduces to a +//: trace: +//: trace("label") << "fact 1: " << val; +//: +//: The tests check for facts: +//: :(scenario foo) +//: 34 # call run() with this input +//: +label: fact 1: 34 # trace should have logged this at the end +//: -label: fact 1: 35 # trace should never contain such a line +//: +//: Since we never call anything but the run() function directly, we never have +//: to rewrite the tests when we reorganize the internals of the program. We +//: just have to make sure our rewrite deduces the same facts about the domain, +//: and that's something we're going to have to do anyway. +//: +//: To avoid the combinatorial explosion of integration tests, we organize the +//: program into different layers, and each fact is logged to the trace with a +//: specific label. Individual tests can focus on specific labels. In essence, +//: validating the facts logged with a specific label is identical to calling +//: some internal subsystem. +//: +//: Traces interact salubriously with layers. Thanks to our ordering +//: directives, each layer can contain its own tests. They may rely on other +//: layers, but when a test fails its usually due to breakage in the same +//: layer. When multiple tests fail, it's usually useful to debug the very +//: first test to fail. This is in contrast with the traditional approach, +//: where changes can cause breakages in faraway subsystems, and picking the +//: right test to debug can be an important skill to pick up. +//: +//: A final wrinkle is for recursive functions; it's often useful to segment +//: calls of different depth in the trace: +//: +eval/1: => 34 # the topmost call to eval should have logged this line +//: (look at new_trace_frame below) +//: +//: To build robust tests, trace facts about your domain rather than details of +//: how you computed them. +//: +//: More details: http://akkartik.name/blog/tracing-tests +//: +//: --- +//: +//: Between layers and domain-driven testing, programming starts to look like a +//: fundamentally different activity. Instead of a) superficial, b) local rules +//: on c) code [like http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet], +//: we allow programmers to engage with the a) deep, b) global structure of the +//: c) domain. If you can systematically track discontinuities in the domain +//: you don't care if the code used gotos as long as it passed the tests. If +//: tests become more robust to run it becomes easier to try out radically +//: different implementations for the same program. If code is super-easy to +//: rewrite, it becomes less important what indentation style it uses, or that +//: the objects are appropriately encapsulated, or that the functions are +//: referentially transparent. +//: +//: Instead of plumbing, programming becomes building and gradually refining a +//: map of the environment the program must operate under. Whether a program is +//: 'correct' at a given point in time is a red herring; what matters is +//: avoiding regression by monotonically nailing down the more 'eventful' parts +//: of the terrain. It helps readers new and old and rewards curiosity to +//: organize large programs in self-similar hiearchies of example scenarios +//: colocated with the code that makes them work. +//: +//: "Programming properly should be regarded as an activity by which +//: programmers form a mental model, rather than as production of a program." +//: -- Peter Naur (http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22) :(before "int main") // End Tracing // hack to ensure most code in this layer comes before anything else |