Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
roth_stephan_clean_c20_sustainable_software_development_patt.pdf
Скачиваний:
29
Добавлен:
27.03.2023
Размер:
7.26 Mб
Скачать

Chapter 7 Functional Programming

for (const auto& value : std::views::iota(0, 100) | std::views::transform(toSquare)

| std::views::filter(isAnEvenNumber)) { print(value);

}

return 0;

}

Clean Code in Functional Programming

No doubt, the functional programming movement has not stopped with C++, and that’s basically good. Many useful concepts have been incorporated into our somewhat aged programming language during the last decade, starting with C++11.

But code that is written in a functional style is not automatically good or clean code. The increasing popularity of functional programming languages during the last few years could make you believe that functional code is per se better to maintain, to read, to test, and is less error prone than, for instance, object-oriented code. But that’s not unconditionally true! On the contrary, nifty elaborated functional code that is doing non-trivial things can be very difficult to understand.

Let’s, for example, look at a simple fold operation that is very similar to one of the previous examples:

// Build the sum of all product prices

const Money sum = std::accumulate(begin(productPrices), end(productPrices), 0.0);

If you read this without the explaining source code comment…is this intention revealing code? Remember what you learned in Chapter 4 about comments. Whenever you feel the urge to write a source code comment, you should first think about how to improve the code so that the comment becomes superfluous.

So, what we really want to read or write is something like this:

const Money totalPrice = buildSumOfAllPrices(productPrices);

333

Chapter 7 Functional Programming

You prefer the functional programming style over OO? Okay, but I’m sure that you will agree that KISS, DRY, and YAGNI (see Chapter 3) are also very good principles in functional programming! Do you think that you can ignore the single responsibility principle (see Chapter 6) in functional programming? Forget it! If a function does more than one thing, it will lead to similar problems as in object orientation. I hope do not have to mention that good and expressive naming (see Chapter 4 about good names) is also enormously important for the understandability and maintainability of code in a functional environment. Always keep in mind that developers spend much more time reading code than writing code.

Note  The principles of good software design still apply, regardless of the programming style you use!

Thus, we can conclude that most design principles used by object-oriented software designers and programmers can also be used by functional programmers.

Personally, I prefer a balanced mix of both programming styles. There are many design challenges that can be solved perfectly using object-oriented paradigms. Polymorphism is a great benefit of OO. We can take advantage of the dependency inversion principle (see the eponymous section in Chapter 6), which allows us to invert source code and runtime dependencies.

Instead, complex mathematical computations and algorithms can be better solved using a functional programming style. And if high and ambitious performance and efficiency requirements must be fulfilled, which will inevitably require a parallelization of certain tasks, functional programming can play its trump card.

Regardless of whether you prefer to write software in an object-oriented way, or in a functional style, or in an appropriate mixture of both, you should always remember the following quote:

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”

—John F. Woods, 1991, in a post to the comp.lang.c++ newsgroup

334

CHAPTER 8

Test-Driven Development

“Project Mercury ran with very short (half-day) iterations that were time boxed. The development team conducted a technical review of all changes, and, interestingly, applied the Extreme Programming practice of test-first development, planning, and writing tests before each micro-increment.”

—Craig Larman and Victor R. Basili,

Iterative and Incremental Development: A Brief History. IEEE, 2003

In Chapter 2, “Build a Safety-Net,” we learned that a suite of well-crafted and fast unit tests can ensure that our code works correctly. So far, so good. But what is so special about test-driven development (TDD) so that this topic justifies a dedicated chapter?

Especially in recent years, the discipline of test-driven development, a so-called test-­ first approach, has gained in popularity. TDD has become an important ingredient of the toolbox of software craftspeople. That’s a little bit surprising, because the basic idea of test-first approaches is nothing new. Project Mercury, which is mentioned in the opening quote, was the first human spaceflight program of the United States and was conducted under the direction of NASA from 1958 through 1963. Although what was practiced in that project about 60 years ago as “test-first development” certainly is not exactly the kind of TDD as we know it today, we can say that the basic idea was present quite early in professional software development.

But then it seemed that this approach was forgotten for decades. In countless projects with billions of lines of code, the tests were postponed at the end of the development process. The sometimes-devastating consequences of this right-shifting of the tests in the project’s schedules are known: if time is getting short in the project, one of the first things usually abandoned by the development team are the important tests.

335

© Stephan Roth 2021

S. Roth, Clean C++20, https://doi.org/10.1007/978-1-4842-5949-8_8

Chapter 8 Test-Driven Development

With the increasing popularity of agile practices in software development and the coming up of a new method called eXtreme Programming (XP) at the beginning of the 2000s, test-driven development was rediscovered. Kent Beck wrote his famous book Test­ -­Driven Development: By Example [Beck02], and test-first approaches like TDD experienced a renaissance and became increasingly important tools in the toolbox of software craftspeople.

In this chapter, I not only explain that although the term “test” is included in test-­ driven development, it is not primarily about quality assurance. TDD offers many more benefits than just a simple validation of the correctness of the code. Rather I explain the differences between TDD and what is sometimes called plain old unit testing (POUT), followed by the discussion of the workflow of TDD in detail, supported by a detailed practical example that shows how to do it in C++.

The Drawbacks of Plain Old Unit Testing (POUT)

No doubt, as we’ve seen in Chapter 2, a suite of unit tests is basically a much better situation than having no tests in place. But in many projects the unit tests are written somehow parallel to the implementation of the code to be tested, sometimes even completely after finalization of the module to be developed. The UML activity diagram depicted in Figure 8-1 visualizes this process.

336

Chapter 8 Test-Driven Development

Figure 8-1.  The typical process flow in traditional unit testing

This widespread approach is occasionally also referred to as plain old unit testing (POUT). Basically, POUT means that the software will be developed “code first”, and not test first; meaning that the unit tests are written always after the code to be tested has been written. And to many developers this order appears to be the only logical sequence. They argue that to test something, obviously the thing to be tested needs to have

337