Clean Coding – How to keep your workplace clean as a developer
Dennis: Hello, welcome to a new episode of our podcast, Computer Scientists Explain the World. I'm back again, Dennis, software developer at Consistec. But I have brought a new face with me today, Kevin. Kevin, introduce yourself.
Kevin: Yes, hello, I'm Kevin. I've been a software developer at Consistec since last year. And before that I was actually a listener of this podcast. Thats how I became aware of Consistec and now I'm sitting here on the couch with you and we're recording an epsiode.
Dennis: Yes, Kevin, why don't you explain to the people what we want to talk about today?
Kevin: We want to talk about clean code and code style today. What makes good code, what is bad code. How do I recognise bad code. And also a bit about the methodology, how we proceed in our team to produce good code.
Dennis: Okay, then I'll start and ask you: "Why is it actually important whether I write good code or bad code? What does that mean for me? Does it somehow have side effects for the client or now for the developer or is it just code aesthetics? What is that about exactly?"
Kevin: That can be reflected on different levels, for one thing. Good code or bad code. The first thing that comes to my mind is that if I have a bad codebase, then the code is usually bad to test or it is very time-consuming to write tests. And in the end, testing is very important in development to verify that your code works and does what you want it to do. That's why you want to have good testable code. Another problem is the understanding that if I write bad code and I'm now sick or on holiday and you now have to maintain my code because a back-off has occurred. Then it's much easier for you to find your way around the code, to see "This was Kevin's intention and that's why he did it this way."
Dennis: Worst case scenario, you left the company. Nobody understands your code anymore. That's what we call legacy code base in the jargon. Nobody really knows how it works. In the worst case, there's also no documentation or tests to indicate whether it even works. And then it's really questionable whether a project can be continued again in the future.
Kevin: Yes, exactly. And another point is that bad code or a legacy code base like that, also affects the company, the customer, because it simply causes costs. Maintenance and support become expensive and implementing new features costs more time and therefore more money for the customer or, depending on the company, when it has to renew something.
Dennis: Yes. I think it's also important to mention in this context that you try to write as little code as possible. Because the more code I have, the more potential problems I have in the code. And then it can happen that the customer contacts me and something doesn't work. So when I improve code, we call it refactoring, we always try to reduce the code a bit. Because that makes it easier for the programmer to keep track of everything. There are already a few points that make it plausible why it is so interesting to write really clean code. Now I'd like to ask you: "When was the last time you wrote bad code? Can you remember?"
Kevin: Yes. I have to think about that…. that's a very clever question. Yes, I can actually remember a situation where I wanted to write a simple auxiliary method that evaluates variables and gives me a boolean in return. And at some point I noticed, "It doesn't behave the way I thought it would." And actually, I thought, "This is trivial, this function." And then I thought, "Okay, it's not behaving the way I want it to. I'm going to write a test with all my test cases and see how the function should behave, test the function." And then it turns red here in two or three places. I looked at it again more closely, thought about it more carefully and was then able to adapt the function. Then it worked the way I wanted it to. When was the last time you wrote bad code?
Dennis: It was actually only a two weeks ago. A colleague of ours, Kim, did a review of a code I wrote. I wrote a relatively extensive test and while writing the test we noticed that there are various input parameters for testing and that could be a function, that could be an object. And then I expanded the test basis more and more. Then this test, this test parameter class became more and more complex. There were generic types, then a second, a third. And it all became relatively complicated. Afterwards it worked great. Kim also said that it works in the review, but I don't know how. And then I approached the matter again. I then refactored it and made different test classes so that it would be easier to understand afterwards. Because someone else might want to write another test in the future. They need to understand exactly how it works. Or even I in ten weeks. I won't exactly remember the way it worked. And then I have to look again at how it worked. Kevin, we've already talked a bit about what we screwed up. Of course, the question is: "What does good code actually look like? How do I write good code? What do I have to pay attention to?
Kevin: On the one hand, it always depends on the programming language we use. Because the programming languages follow different paradigms, have different syntaxes and also a different syntactic sugar. That makes it easy for you to write code. Exactly. But basically, you can also say in a simplified way that if I work in a company and use a certain programming language, then I can always look at Best Projects Lists. And how do others do that? How do large companies, for example, do it? How do they write their code? Are there any lessons learned? What can I do well in principle when I write code?
Dennis: Well, if we break it down to an extreme case and take a language like Java, which is very strongly object-oriented. Of course, I also write very object-oriented code. If I now take Python, for example, which is a scripting language, it's a bit more laissez-faire, I think they say. Maybe I can write more functional code there. And of course that has an effect. Of course, the Python community writes different code. They have different code ideals than the Java community, for example. But that doesn't mean that one of the two is right and the other simply writes the wrong code. It is simply ideomatic code and Python is perhaps written differently than Java. And the second component is, of course, when I go into a company as a developer they will have a certain code style. Certain things: "How do you write conditions, for example? How do you insert that?" Maybe those are details. But these are things that you should clarify in advance with your colleagues in order to know, "What's the best way to write the code?", so that you have a certain consistency in it. Yes, but as you said, there are of course a few objective, I'll just say guidelines on how to write good code. I am now applying this across several programming languages. And we want to go into this in more detail today. Then let's get started, Kevin. What is the first thing that comes to your mind?
Kevin: It just occurs to me, because we are looking at red names and variables, that of course it always makes sense to choose short names, but they should always be meaningful. So, a variable shouldn't be called X, but should have a meaningful name, where the developer who reads it simply knows, "What does the variable stand for?" and in the case of a method or function, you should already be able to recognise from the name, "What is the input here, for example, or what is the output of the function? Because at the end of the day, we developers are mostly busy reading code. And only a fraction of the time we are actually busy writing code ourselves.
Dennis: Exactly. Maybe also a little bit in the head, the rethinking that there is not code and documentation, but that the code often documents itself. If I use good methods and variable names, then I don't have to write extra documentation, because it's self-explanatory and it's much easier for us, as developers, if we don't have to consult the manual first, but can see directly from the code, "What's actually happening?
Kevin: Exactly. So you still shouldn't forget that documentation is always important. There are definitely cases where you might document a method or a class better. Because you say, "OK, maybe I had an intention behind this, why I solved it this way. Then you can also note something like that so that the person reading about it simply understands the intention. But if you choose the names properly and keep the function small, then the code is actually already in its own documentation.
Dennis: I just see with colleagues who are very new and starting out, something we always call Captain Obvious comments and there is a method called Execute Command and above it is a doc string that says Execute Command. So there the Doc String just doesn't do anything for you. That doesn't mean that just because documentation is important, you have to have a Doc String for every method. Sometimes it's simply explaining directly what it does. Sometimes you don't have to explain what the method does, but just some time effects that you don't even expect. I have a method that suddenly writes some Jason file onto a high system. Then, of course, it's cool if I say a doc string at the top that the method also does that. Exactly. You have already mentioned methods, the naming. That is of course a very important point. But what is also important is "How do I write methods? Do I write methods ideally with 500 lines, 300 lines, maybe only ten?" What is your insight into that?
Kevin: What one often hears in the literature or what one often hears or is told is that a function or method should do exactly one thing. That makes for a developer who is starting out you have to ask yourself "What is this one thing? How do I narrow it down?" That is not so easy to define.
Dennis: Yes, Kevin, you said that methods should do one thing and they should do it well. Of course we want to explain that a bit more concretely now. It is of course very abstract. The basic idea is that I get a relatively large method that is supposed to do a relatively large number of things, and I stagger it. So I break this method up into several smaller methods and my large method then calls up the auxiliary methods. And each auxiliary method then has a small part of the whole. It then only does a small part and not the whole thing. And if I do it cleverly, then on the one hand I have a hierarchy, there are methods that are a little higher in the hierarchy, which then use auxiliary methods. And then there are methods that are lower in the hierarchy that do concrete things. Maybe you want to give an example so that we can illustrate it a bit more.
Kevin: Exactly, we can first imagine abstractly what we computer scientists often have when programming, that we somehow have server communication. So, we have a client and it wants to talk to the server, they want to communicate, so the client must also have a functionality, a function, how it can address the server. And for that you need different things. Something like the address, for example, and especially if we are here with the http protocol, we also need a header in which certain meta information is set. And of course we also have our actual message, our message, which is in the body and then the whole thing has to be sent. Sending the message then works at a rather low level via the protocol. And now we could of course go, as you said, they have a function. A function with 100, 200 lines that does everything. It goes there and sets the headers, sets the body, takes care of everything, sends the whole thing, receives the whole thing, maybe even unpacks the answer, and then it all gets confusing very quickly. And then we simply have to go and split the whole thing up, according to a divide and conquer principle, so to speak. Into small functions, pack them into auxiliary functions and then we might just have an auxiliary function that assembles a header for us. We have a function that assembles the body and another function that sends the actual request. And also another function that can handle the response. And if we have divided this into these four parts, for example, and we look at the whole thing, then we can see directly, "What happens in our actual higher-level function?" We have the assembling, the sending and the receiving. If we now want to know more precisely how that works, then we look again at the individual functions and see how it works there. How does it work at the low level?
Dennis: Well, you have a bit of a hierarchy, you have the top level, then this method, which is supposed to send the whol request to the server. So maybe, to pick up on that again, on one side we have the client, for example your computer, on the other side we have a server, which is now somewhere in the computer centre around it. And we use a protocol like http, which basically says exactly how such communication takes place. And what the client then does is-, it just makes a request. Of course, it has to be prepared and then sends it over the internet to the server and the server sends something back. And now you have already identified how to do that. On the client side, we have to do various things. We have to assemble the header, which contains a bit of meta-information, we have to open a connection to the server, we have to send it, we receive another response and we also have to process the response a bit. The idea is that I have this top level "send request to server" method. But this send request to Server" uses auxiliary methods, namely the methods to prepare the header, to open the connection and send a quest and that to read out the response. And now, as a developer, I can say, for example, "I'm kind of analysing a problem right now and I suspect that the problem is related to the header." Then I can just look into the send request method and see, okay, these four methods are called. But now I want to know how that happens with the header. I now go into the Prepare Header method. So in principle, it also makes it easier for you to navigate through the code if you break it down a bit. In comparison-, you could of course-, there's no stopping you from doing everything in one method. You can do everything at once in a send request method, there are 400 lines of code in there, but then you have to read the whole code of the method to understand where these headers are set. And if you divide it up nicely, have a nice hierarchy, then you can look at exactly the segments that are actually interesting for you now. And that is also one of the reasons why it is important to write good code. Because it's also simply in the future- that for us to be more effective, that we can develop faster, that we can analyse errors better, because the navigation through the code simply becomes clearer.
Kevin: Another point that occurs to me is that, when we talk about bad code, it's something like the parameters of a function. We can also talk about how many parameters we use and what the advantages or disadvantages are.
Dennis: Yes. Maybe we can use your example, what you said with the auxiliary method at the beginning, where you had a lot of parameters and realised, "Oh, it's not doing what it's supposed to do". There is this classic example, I have a method, I start there, it has a functionality and then a new feature is added and then we simply put another flac as a parameter into the method and then I look at the flac because it is now true or false and then the method behaves differently. And then a second flac is added and a third. And what actually happened is that we packed a lot of different tasks into the same method and concealed the whole thing by adding such flacs. That's always such a classic example and it indicates that I'm actually doing too much in this method and that I should have actually split it up. And I would assume that if you have a lot of parameters - we're talking about three, four, five, six parameters - that's a relatively good indication that your method is doing more than it should. And on the other hand, it's also very difficult to find a good name for a method when it has so many parameters, because its functionality always depends on what you put into it.
Kevin: Exactly. And above all, the problem is that with methods that have so many parameters or so many flacs, they simply become unpredictable and you can always expect a deterministic outcome from the function. And then you sit there, especially when an error occurs, you sit there for a very long time to understand "Why did the error occur and in which cases, in which-, when do the flacs have to be trues or faults so that exactly the error occurs? And, exactly. If you just have smaller methods, then it's easier to debugg and easier to fix in the end.
Dennis: And also to test. If I now want to test a method with five different parameters, then I have to run through all possible test cases for all parameters. And that can be very complex and it can quickly happen that you overlook something, a so-called edge case, and your method can't handle it properly. So, basically the idea is, when I have methods with many parameters, to think about "Does this method still do one thing or does it already do many different things? And have I perhaps reached the point where I break this one method into several small methods? That is actually already a good keyword, breaking up into several methods. I would also like to talk to you about refactoring. So, what is refactoring and how do you go about it? Or how do you basically go about programming? Do I now write code that is perfect directly or do I feel my way towards it? How do we code, Kevin?
Kevin: I would like to talk about refactoring first. What is refactoring? We simply have an older codebase. Maybe one or several functions that do something specific and we just see, okay, that's bad code, I'll say, the functions are too big, they're hard to test. Then you are well advised to take the time to refactor the whole thing, that is, to rewrite it. And then you can go there and simply write tests that test the functionality in general, so that the tests are green. And in the next step, you can go and say, "OK, I'm going to split up the functionalities. I'll think about the structure again, refactor the whole thing and at the end I can see whether my tests still remain green" and then the refactoring was successful.
Dennis: Of course, I could say that it's difficult to refactor now and write tests beforehand, because you're changing your code. But the idea is that I look at the code and then there is a certain point where I draw the line and say, "Everything below that is a bit suboptimal, I would actually like to refactor that." But everything above that, the methods that are now, let's say, higher in the hierarchy, they are now okay. And then I can write tests for the methods that are a little higher in the hierarchy, because when I have refactored it, the same result should still come out. So, when I have written the tests, then I can effectively start changing the code underneath. Because when I've changed it, I'm still sure that it works, because the tests are green. The alternative is to trust my programming skills and go in and refactor. But then it's difficult to say or guarantee afterwards that it really works in all cases. Then I have the second thing, Kevin. Writing code. I think that many, many people who start programming put a bit of pressure on themselves. They think, if I write code now, it has to be perfect straight away. But I think that's something you can't really do in practice. How do you go about it? So, how do you normally write code?
Kevin: I would definitely agree with you there. So, I think prospective developers and also experienced developers should maybe generally put aside the idea that you write perfect code directly. That might happen in trivial cases, but in the rarest cases the code is really perfect. Exactly. And I'm still a little bit in the process of finding it. "How do I actually go about writing code?" What has always worked for me is that I have written a basic framework. I found my way around in such a code, I see, "OK, I have two or three places here where I have to adapt the code, a new function has to be added". Then I write down the function, the basic framework, so that I have a rough functionality. And then in the next step my approach is that I write my tests and I cover the functionality with tests. I look at what do I put into the function, what should come out?" And then I start to implement the function correctly, so that my tests will cfirst turn green.
Dennis: Well, that's the Test Driven Development approach. First the tests and then the development. What this involves, of course, is that I first really thought about exactly what I wanted to do. So what we rarely do is that we have a feature request, for example, and then start programming straight away. For us, it's often the case that we first discuss things a bit with a colleague. Have we really understood exactly what needs to be done? And then we also discuss the concrete solution a bit. "Do I do it this way or that way?" Because the sooner you clear up these misunderstandings before you start programming, the easier it is to change them. Once I've written two thousand lines of code and realised that the plan doesn't work out the way I thought it would, then I have to delete everything and start all over again. So first it's this process of finding out, "What exactly do I want to do?" And only when I'm sure, do I start thinking about "How do I test this?" And then I start writing these tests.
Kevin: Personally, it always helps me to go to a colleague and talk to him about a feature, "How would I solve that? Does he see any problems? Does he have a different point of view? Can he give me a few more tips?" Exactly. And then I implement the whole thing and for me the development is just such an interactive process, such an evolution that the code then goes through. I would say that first the code is bad and works. And then I look at it again and say "OK, now I'm making it good and now I'm refactoring it". Although it sounds in auxiliary methods, improve the name maybe of variables or of functions and make it better. The good thing is, because I already have my tests and I know it works, then I can also check afterwards after refactoring if it works. And then it's always such an evolutionary process that the code is bad at first and then gets better.
Dennis: So it's a bit of a mantra, the idea of how a sculptor creates a sculpture. First I have the rough stone and then I have to work out the proportions. It looks abstract at first, but that helps me with the next step. And then maybe the body shape comes in and only at the end do I work on the eyes for example or on the facial expression, on the face. No sculptor, or-, I'm probably going to go out on a limb and say that few sculptors would now say "I've got a stone and now I'll first do the nose and the face and that's that and then I'll start to work out the body shape. And it's similar with us. So, we are now at the point where we know what we want to programm, we have written the tests and now it really starts. We open the editor, we want to write code and then we write something that works. The tests turn green? Great. I already know that it does what I want. And then I can start looking and then I say, "Okay, this part of the code, I've got it several times. That's code redundancy. You want to avoid redundant code at all costs. And then I say, "OK, but then I'll make an auxiliary method out of it and that's how it's formed. As I said, this is an iterative process. I then look over it again and again, and what can I still change, what can I still improve? And at some point you reach the point where you say to yourself, "Now I've improved it as much as I can. And then comes the next step, which is the code review. And of course we want to talk about that. Why do we do code reviews? What is a code review? Why is a code review important for us at all?
Kevin: Well, I think as a developer, when you work on a piece of code for a longer period of time, you always fall into a kind of tunnel vision. I like to say that you become a bit code-blind. You might not see obvious errors or 'unattractiveness' in the code anymore. And then you can do code reviews, as we do on a regular basis. That means another developer gets the code, looks "Is the functionality right?" and then looks at the code, "Do I understand that?". This has several advantages. For one you simply get a new perspective on your code and on the functionality. We might still notice things. On the other hand, a code review has the advantage that there are two people in the company who are familiar with code and you have a simple transfer of knowledge. And that is very important. Because you don't work as a lone warrior on this project. You work in a team and everyone has to be familiar with the code. And you should also make sure that there is a transfer of knowledge, and I think code reviews are ideal for enabling knowledge transfer and learning. Both sides can learn from it.
Dennis: Yes. I think it's really important that you get a different perspective. It is often not clear what is better, what is worse. Sometimes it's also a question of conviction. Which code style is the best? But it's always nice to see the other side in a code review. If you do a review of mine and write underneath, "Watch this, I would have written it a bit differently", for example. Then I can decide whether I do it that way or not. But in any case, I'll take your perspective with me. And I think many people misunderstand that. Code review is not about controlling, it's actually about objectively improving the quality of the code. Also, for example, to look at "are there enough tests?" This is a classic case where the reviewer says, "Watch out, you haven't tested this case." The point is to make it waterproof. And on the other hand sharing knowledge. And I think that's the point that many people don't realise. Okay, now of course we've already brought up a few interesting points about why the code reviews are interesting. Now I'd like to ask what else do you think is actually, from an organisational point of view, is the prerequisite for really being able to introduce a code review culture.
Kevin: What I find very important is simply to have a proper error culture in the company and also in your team. I would simply say that making mistakes is not bad at all, because you learn from mistakes. And only by learning can you become better. That's why you should be really open to reviews at this point. And if someone makes a comment, you should always be able to discuss it or perhaps exchange ideas, to say, "Okay, I solved it this way for this or that reason". And maybe the other person understands that and says, "Okay, then you're really right," or he says, "But you can solve it differently," but then you should just accept something like that and learn from it.
Dennis: Yes. So, not this defensive attitude that the criticism affects someone personally. It is simply something objective. It's simply a matter of code. We all want to make sure that the code quality is really good. And that's why we take advice on a professional level. The other thing you said I think is important, is how to deal with mistakes. What comes to my mind is this blame culture. When someone makes a mistake, three people are there to put their finger in the wound. Of course, that's very bad when you want to do code reviews, because it leads to this defensive behaviour. Then people close down. They are afraid to admit a mistake because it might have consequences. Then there's a staff meeting, then it's taken up again. You simply have to accept that programmers are only human. Of course there are ways and means to improve the quality of software through testing, but there are always those one percent use cases where things go wrong. And mistakes happen and we all have to learn to deal with them professionally and to learn from them. Because what can not happen is that the same mistake happens again and again. That is the point where it is really difficult to explain to the user or the customer why the error occurs again.
Kevin: We have now reached the end of our podcast, Dennis. How do you write good code?
Dennis: As we said. Actually, at the beginning there is bad code and you work your way to it. But why?
Kevin: Because the code simply develops and is improved incromentel and evolutionarily. The code then becomes better bit by bit.
Dennis: It's a bit like the sculptor principle. That you first work roughly and then only later work out the finer details of the code. And that works best, as you said, through such an incromentel process.
Kevin: And it is important that good code documents itself. We also talked about that in detail. With the variable names and the method names and that the methods should be small.
Dennis: Yes. And we also talked about the fact that code doesn't come from one person, there's always a team behind it. There are reviews, there are feedback loops, the whole team takes responsibility for it and tries to support each other. This is not a lone warrior who has been given a task and has to carry it through to the bitter end.
Kevin: Exactly. And at the end of the day, behind every good code is a good team. That's it from me. I'm Kevin.
Dennis: That's it from me, from Dennis and I'll see you next time. Bye.
That's it from us again!
We hope you enjoyed today's episode and that we were able to bring you a little closer to the topic of "Clean Coding".
You can find further links to our current episode in the show notes and if you are interested in the wonderful world of software development, we would of course be happy if you subscribe to us.
You can find our next episode on all our podcast channels from 05 May!
Until then, have a sunny time and stay healthy!