{John Ousterhout on A Philosophy of Software Design} {Software Engineering Radio} {episode 520} {2022 07 12}

John Ousterhout on A Philosophy of Software Design , Software Engineering Radio, 2022 07 12.

Abridged Transcript

Doolittle
Is there just one good design style, or are there many...?
Ousterhout
My current working hypothesis is that are these absolute principles.
Doolittle
In the software development lifecycle, when do you design?
Ousterhout
You simply can not design a computer system of any size and get it right, ... so you have to be prepared to fix those... You have to be prepared to do some redesign after the fact... It often takes about three tries to get a design right.
Doolittle
You mentioned another principle of pulling complexity downward...
Ousterhout
The über principle -- the one principle to rule them all -- is complexity... The only thing that limits what we can build in software is complexity... The computer systems will allow us to build software systems that are far too large for us to understand... so everything is about complexity... All of the principles in the book are about managing complexity... If it seems like one of these principles that I've put forward conflicts with managing complexity, go with managing complexity. ...Complexity is incremental. ...It isn't that you make one fundamental mistake that causes the system's complexity to grow... It's lots of little things. ...They add up. ... Once complexity arises also, it's extremely difficult to get rid of because there isn't one single fix. ... Most organizations don't have the courage an level of commitment to go back an make major changes like that, so then you just end up living with it forever.
Ousterhout
In the same way that complexity builds up piece by piece, you can do design piece by piece.
Ousterhout
We want to somehow find ways of hiding complexity. So if I can build a module that solves really hard gnarly problems -- maybe it has to have some complexity internally, but it provides this really simple clean interface for everyone else in the system to use, then that's reducing the overall complexity of the system, because only a small number of people will be affected by the complexity inside the module.
Doolittle
Yeah, that sounds very similar to what one of my mentors calls "technical empathy".
Ousterhout
I can guess what the meaning of that is. I like the idea, yes."
Ousterhout
Another great example of that is configuration parameters. Rather than figure out how to solve a problem, just export twelve dials to the user. And then not only are you punting the problem, but you can say, "Oh I'm actually doing you a favor, because I'm giving you the ability to control all of this so you're going to be able to produce a really great solution for yourself. But oftentimes I think the reason people export the parameters is because they don't actually have any idea how to set them themselves, and they're somehow hoping that the user will somehow have more knowledge than they do and be able to figure out the right way to set them... That's an example of pushing complexity upwards.
Doolittle
Defining errors and special cases out of existence. What are some examples of how you've applied this, or seen this principle applied?
Ousterhout
So first I need to make a disclaimer on this one. This is a principle that can be applied sometimes, but I have noticed, as I see people using it, they often misapply it. So let me first talk about how you can apply it, and then we can talk about how its misapplied. Some great examples: One of them was the unset command in the Tc scripting language. So Tcl has a command that unsets a variable. When I wrote Tcl, I though, "No one in their right mind would ever delete a variable that doesn't exist. That's got to be an error." Well, it turns out, people do this all the time. ...And so what's ended up happening is that, if you look at Tcl code, virtually every unset command in Tcl is actually encapsulated inside a catch command that will catch the exception and throw it away. So what I should have done was simply redefine the meaning of the unset command: Instead of deleting a variable, the new definition is, "Make a variable not exist." An even better example, I think is deleting a file. So what do you do if somebody wants to delete a file when a file is open? Well, Windows took a really bad approach to this, and said, "You can't do that." ... Unix took a beautiful approach. It's really a lovely piece of design, which is, they said, "Well, it's no problem. You can delete a file when it's open. What we'll do is, we'll remove the directory entry -- the file is completely gone as far as the rest of the world is concerned -- but we'll actually keep the file around as long as someone has it open, and then when the last process closes the file, then we'll delete it," which is a perfect solution to the problem. ... I need to make a disclaimer here, though, so people don't go off and make a whole lot of mistakes. I mention this principle to students in my class, and actually I'm at the point now that I may ... I may even stop mentioning this to the students, because no matter how much I disclaim this, they seem to think that they can simply define all errors out of existence. ... Errors happen. Most of the time you have to actually deal with them in some way, but sometimes, if you think about it, you can actually define them away.
Ousterhout
regarding "unset"] I made something that wasn't important important. That was my mistake.
Doolittle
Don't make unimportant things important.
Ousterhout
And vice versa. One of the mistakes people make in abstraction is they hide things that are important.
Ousterhout
Figuring out what's important is hard ... Make a guess. Think about it. Make your best guess and commit to that. Form a hypothesis, and then test the hypothesis. ... Your code will speak to you if only you will listen. ... There are red flags, things that you can see, that will tell you that you're probably on the wrong track in terms of the design. ... Use what you can observe about a system in order to learn what's good and bad, and also in order to improve your design skills
Ousterhout
I think of a module as something that encapsulates a related set of functions. A module is a vehicle for reducing overall system complexity. The goal of a module is the same as the goal of abstraction: To provide a simple way to think about something that's actually complicated, to have a simple interface to something with a lot of functionality. In the book I use "deep" to describe modules like that. ... The ideal modules are those that have very small interfaces ... and a lot of functionality. Shallow modules are those that have a lot of interface, and not much functionality. The reason that's bad is that that interface is complexity'.
Ousterhout
Really good modules just do the right thing. They don't have to be told. They just do the right thing. ... The world's deepest interface is a garbage collector. When you add a garbage collector to a system, it reduces the interface ... You no-longer have to call free().
Ousterhout
SQL is a beautiful example of a very deep interface. Another one, one of my favorites, is a spreadsheet. ... You just have a tow-dimensional grid in which people can enter numbers or formulas.
Ousterhout
I don't believe there is any way to prevent people from producing complicated systems, or, for that matter, to prevent people from introducing bugs. Sometimes systems go out of their way to try to prevent people from doing bad things, but in my experience, as often as not, those systems also prevent people from doing good things.
Ousterhout
Most of your investment in software is in the future, for any project. The most important thing is to make that future development go fast. ... You're basically borrowing from the future When you code tactically. ... A tactical tornado is the ultimate tactical programmer: "Do whatever it takes to make progress today, no matter how much damage it causes to the system." ... What's ironic bout them is that often, management considers these people heroes. I think what you need is management that doesn't support those people, that recognizes that these people are doing damage.
Ousterhout
I'm not a fan of test-driven development. I think it's better to build a whole abstraction, and then to write the tests afterwards. When I write tests, I tend to do white-box testing. That is, I look at the code I'm testing and I write tests to test that code. That way I can make sure, for example, that every loop has been tested, and every condition has been tested, and so on. When I write tests, I don't test to test the abstraction. I tend to test the implementation. I feel like I can test more thoroughly. If I don't look at the implementation at all, I think it's more likely that there are going to be things that I'm not going to notice to test. The failure of my approach to testing ... It's very good at catching errors of commission... It's not so good at testing errors of ommission.
Ousterhout
There is one place where test-driven development is a good idea. That's when fixing bugs. Before you fix a bug, you should add a unit test that triggers the bug, make sure the unit test fails, then fix the bug and make sure the test passes.
Doolittle
"Comments should describe things that are not obvious from the code." I have feeling that this principle might also be slightly controversial.
Ousterhout
This principle is controversial. There seems to be a fairly large group of people who think that comments are not needed, or even that comments are a bad idea. For example, Robert Martin, in his book, "Clean Code", ... says "every comment is a failure", and the implication is that if you had to write a comment, it means you failed to make everything clear from your code. I disagree with this point. I think that, fundamentally, it is not possible to describe in code all the things that people need to know in order to understand that code, ... and that's the purpose of comments. ... If you really want to hide complexity, you have to have comments that do that.

Page Authors

pyk