Spaghetti code is the death of a project. After 40+ years as a software engineer (retired now) this has been my experience. And as Kyle found out taking over a project it is very difficult and refactoring is even more difficult. Clean code, small individual functions and remember the code should be readable by humans first. Kyle does great videos.
On other hand, some of the practices of the so Called Clean code book , when followed blindly instead of seeing where they apply reasonably, result in another type of spagheti (class bloat, that is just a different type of confusion)
I'm pretty new in software dev and it's one of the first things I've learned the hard way: Keep your concerns separate. You should always be able to completely replace one thing, and everything else still works. X goes in, Y goes out, the rest of the code doesn't care what happens in between. That way, you never even get in the situation described in the video here, where you do a "refactor" that essentially amounts to a rewrite of the entire project. You shouldn't have to do a "refactored" branch that you work on in parallel to the productive branch. You should be able to push small, bit-by-bit refactoring to the productive branch. I'm not very experienced so maybe I'm being naive here, but maybe the best way to deal with "spaghetti code" is to leave it alone while it works, and when you do have to get into it, cut it to pieces while you're at it. Let's say you' go into legacy code to fix a big function that does 3 things. Split it into 3 while you're there, if possible. It may not be necessary at the moment and it may not offer immediate benefits, but it very well may pay off the next time you or anyone has to go there. Even if the logic is just as messy as it was before, separating concerns can go a loooong way and make any future refactoring efforts so much easier.
Great advice! (20-year software engineer here). I'm surprised you didn't use the term "tech-debt", that's basically what you're describing. I think of addressing tech debt the same as addressing financial debt. You don't pay off 100% of your credit cards and then not pay your rent and buy food that month... You pay off a chunk at a time when you can afford it. If you address tech debt the same way (basically what you were describing) eventually your tech debt will be at a more reasonable level.
tech-debt is overloaded term, but it is used all the time, it almost seem tech is not moving, so you go back and pay the price, but in fact when you or your team have a chance to refactor the code, the tech you use could already be out-dated. because tech is moving fast :)
I was literally doing unnecessary refactoring this exact moment haha, what's so special about kyle is that he doesn't just teach you how to program, but the best, most optimal practices!
well, you can do test first unit testing to make things easier while you refactor. Saves you time on testing. Of course you have to be well verse on unit testing before jumping into it. When we refactor by batch, we strictly requires unit testing automation
Refactor as you go. Ideally, think about the problem you’re solving in an abstract enough way that future iterations don’t need to be refactored to be extended. If you’re about to put a band-aid over another band-aid, refactor that section.
Hell yes. I'm glad you mentioned this: a refactoring is an isolated micro change to improve the design of existing code. If it is taking hours, let alone days, weeks or months, that is something else entirely. You _may_ end up making substantial changes over a long period using thousands of small refactors, but the overall activity is no more "refactoring" than running a marathon could be called "taking a step".
Fully agree. And even if you do a large-scale refactor; do it in small steps. With your anecdote: a lot less time would have been wasted if you regularly merged the code into main and got to experience the benefits with implementing the new features. It also makes sure it is more in sync, you don't want a branch of a version that is a month old. The only large-scale refactor I had to do was a case where our data model did not match the data model of the source. So when they had a new feature, we could not implement it. But that refactor had a lot of planning to break it down in small steps.
Clean and maintainable code always wins in the long run. You should always write well structured, understandable code. The thing you don't need is extremely optimized code for performance, that can come later when you actually reach a bottleneck and improving performance becomes a goal. Oftentimes, the time you'd spend gaining a few extra cpu cycles wouldn't even be noticed by the consumer. As for refactoring, you never have idle time to work on that. The way I do it, is I only refactor the classes and methods that I'm using directly in this new feature or patch that I'm working on. It's that idea of always leaving the place cleaner than when you entered. You do small (or large) refactorings along the way to achieve your goal, and the refactoring never becomes an activity in and of itself, and then you don't ever need to convince your managers and higher ups to alocate time for this (because they won't want to, they usually only care about the finished product).
Sure. If they only care about the finished product then they shouldn’t care how it’s been programmed either. Also not creating a class for everything, not having documentation comments, not writing tests and using basic commands of the programming language rather than the latest hot shit which takes time to learn, allows you to finish the project in a quarter of the time allocated for it during scrum meetings. That does not mean the code will be disorganised. It will be logical and will function. If any extension to it is needed then the programmers should be able to work on it if they are indeed programmers.
Stupid saying that perfectly outlines the lack of planning in this society and also the perfection of constantly finding excuses for responsibilities. If you have a number of developers, the first thing to do is to design common standards and guidelines so that everyone does NOT do their own thing. Millions of projects show exactly this wrong behavior. incredibly ugly code in which everyone wanted to leave a little bit of their own "scent" because they consider themselves to be the best of all developers and in the end a project that triggers nausea and that no one can really work on properly.
Hi Kyle, I've been a software engineer for longer than I'd like to admit to 🤫 and I totally agree that the refactoring effort can be a pointless and expensive portion of some projects. I worked on a large banking project for a well known UK finance house and spent (along with my team) way too long on refactoring code only to add more issues and cost. I understand the technical reasons but sometimes 'less is more' - it's a cliche I know but doing nothing sometimes beats doing lots !! - love your videos and all the best from UK !
I believe it's ok to refactor when you just start coding in general. I have made like 4 iterations of my user style sheet for RUclips, the first looks like a long list of single selectors with 1 or more properties, the second has grouped them together based on the visible feature, and finally uses more selectors in one given rule. The third no longer groups them per feature, and puts the feature name in a comment before the selector, the fourth is below the third, calling it "restructure", and it mostly gave up on the feature names, is now grouping them based on the property, and only one property per rule. (unless it's a special rule in misc, then it can have more properties)
This is something I always have trouble balancing. We had some really bad developers in the past. I'm frequently refactoring their projects, removing 90% of unnecessary code, getting massive performance gains, and fixing a few corner case bugs. There's so much to fix though, and I need to work on other projects too.
oh, balancing refactoring legacy code can be a challenge, It's great that you're making progress by removing unnecessary code and improving performance. Prioritize the most critical issues and break down the work into manageable tasks. Remember, it's a gradual process, so don't feel overwhelmed. Keep up the good work.
Refactoring iteratively is excellent advice. In your scenario, if you have new feature requests coming in and you don't know how much time you have available then you can't commit to a huge refactor. I refactored constantly, but often it was just the code file I was working in and small and easy refactors to make the code easier to read and update. I did plenty of large refactors, but I always knew I had the time available to do those before I had to add new features. Basically, if you're going to do a large refactor, then make sure the boss knows you need X weeks to just work on that. Your 80/20 advice is also excellent. If you can easily read and understand what the code is doing and it reasonably efficient, then your time is better spent elsewhere.
What works for me is to put all the messy spaghetti code into a function, tuck it away in the 'utils' folder, and enjoy clean code in the rest of the project. It's like hiding the mess under the rug and never looking under it - very effective! 😄
Unfortunately sometimes you have to add spaghetti code in multiple files. Imagine if you have a system that adds stuff to a database, render an html page, sends email, sends notification and other bunch of stuff and each have their own file and your changes would break all of them unless you spaghetti code all these files.
@@soniablanche5672 Usually, such a problem is solved by organizing the code, isolating individual functional parts from each other, thus preventing them from disrupting the operation of other parts. Here, you may have to compromise on the DRY (Don't Repeat Yourself) principles, but in my opinion, it's better than getting errors in unexpected places.
You nearly got me with this one haha, overall great advice, using an iterative approach means that you can do small refactorings, and target areas of the code you are going to change next, I find that writing tests to capture the behaviour of your code before starting a refactoring is pretty much essential too, if you code doesn't have tests before you do this, it will after, this is also a good way to get into TDD
"Refactor small bits at a time". If you lookup what 'refactor' originally meant, it's always small, by definition. People use it as a fancy way of saying 'rewrite' but that's not what it originally meant.
Refactoring is the process to generalize ideas, for example, putting common code inside parent classes or aggregating common strategies across the code. A good refactoring pull request has more deleted lines than added lines. If there is more added lines, it's not a refactoring
Additional tip to tip #2 ( There is no perfect code): Accept that some parts will be messy, but if you can: try to separate the messy/complex stuff from the other parts of the code as well as you can. This is where the single responsibility principle really shines. If you have a complex part that you simply can not simplify because it just is that complex, please make sure that the complexity doesn't leak out. Put it in it's own file(s) or module and make sure it's api is strict.
Very good advice. Sometimes, some code is messy because of optimization (speed, memory load...) or external components with messy API. But if it's wrapped in a class with a clean API, it's ok. SRP is the first principle for avoiding spaghetti code.
The challenging part about refactoring is figuring out the domain model of the problem at hand. Most devs aren't good at this for some weird reason because we only test maths skills at university. All this DRY stuff doesn't help much. If the inherent problem complexity requires 2 things to be almost the same they're still separate things. DRY makes it tempting to introduce coupling to things that shouldn't be coupled. The "soft" part of software is actually important. You must preserve the ability to change the code. If you see a "utils" or "tools" or "misc" file, alarm bells should be ringing.
All in all, good info. I typically start with, plan, get it working, then look at flow and performance improvements, set a time limit for each part. Definitely set limits and add dev notes if you have ideas for the next iteration or next developer to consider. Smaller changes are usually more focused. If you have a team, peer review can also be helpful. There is also value in putting it down for a while and coming back to it.
I am experiencing something like what you went through when you were a fullstack developer. I do all the coding at my company (frontend with React and React Native, backend, deployment, etc.). I always want to refactor all the code because the me of two months ago was dumb. The worst thing is writing refactored code, which is supposed to be better, and it's not. I hate programming alone, but I think it's good in a certain way. Thanks for the tips! :)
In our project, we started refactoring by just hunting all if nestings and made them into if guards. It made us feel good about the code. The next time we refactor we'll focus on something small like that and change it across the code.
It all depends what a "clean code" means to you :) For me clean code is just to follow the project rules, for example never put styling and logic mixed togther, DRY - move logic functions to separate files, sometimes just ask somebody else if your code is understandable. That is all you need for clean code.
> ask somebody else Even further - honest reviews in pull requests. Don't accept my code just for the sake of it, if something bothers you just tell me. A lot of colleagues seem to really hate going through Pull Requests. I love them: Here I'm seeing other peoples approaches and techniques to learn from and get feedback when my own code might needs improvement. Even if a single colleague tells me they don't understand it for any ridiculous reason, that means I might be able to solve that already by an inline comment or just split a single function into two. Everyone's concerns are valid to me - even those of the dumb colleague I dislike, because starting a fight or proving them wrong doesn't let us progress as a team. :)
my experience is that you need to find a good balance between refactor and progress. Also often times you can get better at designing new features in a clean way to begin with. Of course there will always be imperfections but just stick to the rules at least and clean up stuff when you touch the code again Another big problem I have encountered is that programmers often tend to make stuff usable for every future undefined scenario. But they actually just dont know so should just stick to what they know right now. Often times they also reuse old stuff for new requirements and start implementing different modes inside a component for instance in angular. Its no shame to do a new component and make sure it does one job but one job good
A fellow developer taught me this concept in this word "Refactoring debt" and it instantly clicked. When you go and implement a lot of features without refactoring old code as you go, it becomes an increasing debt. If you keep this going the debt will be too heavy to pay. So its better to refactor code as you go by doing small chunks each time.
It seems like if you're spending that much time to refactor the code, it seems like it wasnt "clean" in the first place, Open close principle for example suggest that its open to extend but not modify.
Being 1 developer on the project won't make you truly appreciate refactoring. Have you ever tried to work with a codebases supported by one hundred developers when no one refactor anything because the whole thing is already so fregile that jenga looks like a fortress next to it? I was and it's a complete nightmare. Refactoring should be embedded in the process on the most fundamental level and yes, it should be iterative, than it will work very good
I'm in the process of cleaning the code that's almost 20 years old. I've started when it was almost 19 years old. Fortunately, I don't have any deadlines and I'm almost done. According to the script that's checking the progress, I have around 500 elements in 77 files to go. I'm doing small changes at a time, so it won't be a full blown revolution, but rather an evolution into the right direction.
It depends. I think some "clean code" best practices just aren't worth the hassle like hardcore inheritance, annotations, getter and setters when they aren't needed. But some have in my opinion have a massive impact long term. The biggest one being exact typing in typescript. For example if you have a function that takes a string but in reality only does something useful with the strings 'yes', 'no' or 'maybe' than specify this with a union type. When you look at the function a year from now you are gonna be glad you did.
Absolutely, While some "clean code" practices may not always be worth the hassle, others, like exact typing in TypeScript, can have a significant long-term impact. Using union types to specify the valid input values for a function can improve code clarity and maintainability. It helps future developers understand the intended usage and prevents potential issues down the line. It's definitely a good practice to adopt for better code readability and maintainability.
Best tip me helped to write clean code was TDD. Define what it should do, code don't have to be perfect, and can be optimized, and can have bugs. But you define what it should do in the test, and everybody knows what it should do, and can make it work, or more readable. So if you don't have time to make it work, you know already what it should do, and a new developer can make it work. If you have bugs, add that scenario to the test, and then write the code. Think about data, not about code. So what should be the input and output.
When you are the solo developer, your code is fine no matter how. But when you work in a team, spaghetti code is a big NO. It is not reusable, repeat yourself and how you do unit test?
This is a great topic and it sort of comes down to idealism versus pragmatism. Engineers often look at a problem and assume that they have a better solution than the current one, and frequently they do. But it is yet not realized, it's only theoretical. As such, it's impossible to know all of the unintended issues it may create, which in some cases completely offsets any benefit gained. There are no perfect solutions.
We are in Project Business. So this means we have a very tough time schedule to deliver the final version of the project software. In parallel we try to standardize our project software to be reused as a product after the project has finished. Now the tricky part. Every 3 month full of new feaure development we get the chance to do some imporvements in a couple of days (at most 5). But you are only allowed to do so, when you are not too late delivering your 3-months-potion of the whole project. We are kind of screwed and are sitting in a big ball of mud at the moment. I tried to group up developers to not implement new features when improvements are stacking up endlessly.
great job man, good to see you actually made a video for this. right on point. but I assume a lot of engineers might disagree, because large tech corps always tell their employees writing clean code and add more unit tests to be "engineering excellent". One thing most engineers do not even think about is those companies can afford their employees to write clean code and unit tests and continuous refactoring, and for sure clean code makes your peers or supervisors feel good when they look at it, how many times you've heard "remove these comment, it is not clean" when doing PR code reviews ? if you leave your job, they will hire another employee to do the same thing. Writing clean code is not a bad thing per say, but it will not do anything. it is simply a bundle of files. the product needs to reiterate, it needs to serve consumers, clean code will not do that. How many engineers knew those big tech firms at the time when there were only a few employees ?
This is exactly what is happening to me right now. I am alone working on a project for a ver small company and it's hard. I'm being perfectionist most of the time.
i'm happy with my code when people who don't much about programing or development can read it and have an idea of what im doing or better yet have an idea of where my problem could be coming from. or know where if they had to add code where it could go. all these things happened to me and it was pretty satisfying seeing people react to my code that way. i try to avoid going full C++ snob mode where you spent to much time trying to decode my code first before you can understand what its doing.
In my experience, it was usually better to forbid those people from changing critical code. Sometimes it isn't practical to make things understandable by everyone.
I think this video compares apples to oranges. The first part talks about "the big refactoring", but it doesn't sound like a refactoring to me. It sounds more like rewriting at least a large chunk of the project. To quote Wikipedia: "code refactoring is the process of restructuring existing computer code-changing the factoring-without changing its external behavior. Refactoring is intended to improve the design, structure, and/or implementation of the software (its non-functional attributes), while preserving its functionality." The fact that you're not changing the code functionality during refactoring is quite important. It means that your unit tests should continue to work after the refactoring. Here is the process that I'm trying to follow regarding refactoring: 1. Don't start refactoring the code just for the sake of refactoring. Have a goal, like adding a new feature. 2. Identify the part that needs refactoring (if any) and what kind of refactoring does it need, to accommodate the new feature. 3. Make sure there are adequate unit tests for the relevant code. As a minimum you need tests that cover all the ways that code is currently used. 3a. If you're especially paranoid, and you should be, you can also see if the tests actually work by deliberately breaking the code to see if the tests will fail. 3b. Commit. 4. Do the refactoring. Run the tests. If they pass you can commit. 5. Implement the new feature. Write tests for it. 6. Was the refactoring actually good? Perhaps you found a better way to approach the whole thing? If so, throw away the code from point 4 onward. Go back to 3. 7. Go for code-review and merge in the main branch. Of course this doesn't always work. For example, there are features that are just too big to do in one single cycle. For these you can use the Mikado method. Google it. I think it's great. Another great use for refactoring is to help you read and understand legacy code[1]. In this case you don't even need the unit tests. You just change the code the way you think it should be and see what happens. When you're done playing, throw away all changes. You now have a better understanding of how the code works and perhaps why it's written the way it is. If you still think it should be refactored, you should go through the above procedure. -- [1] "Legacy code" is the technical term for code written by somebody else or code that you've written more than 2 weeks ago.
You should slowly add unit tests too as you work on new/old code. I inherited a large code base last year that was written mostly bad. I started alone, but after a few months we hired one more dev. I got a good 25% of the code refactored to clean code. We’re now at about 50%. It takes time and patience, but unit tests are a must. If you refactor and then you introduce regression, you want to catch it as soon as possible. Sometimes they slip through, but it’s worse without any tests. Documenting is also a must, so you have use case steps to follow to ensure it’s all good
Agree. Unit tests will also help you structure the logic, make sure you explicitly know what context you're in and make sure the methods are not too complex because then it becomes difficult to test. Also, write better assertions as you go if using typescript. Because that will jump at you even before the unit tests.
What do you do when you have existing tests that are inherently coupled to the existing implementation? Tests are not unit tests and don't test the API.
@@majorhumbert676 depends on what you’re testing. Black box testing will *always* be coupled to the implementation. White box testing shouldn’t be. Although if a function changes the post outcomes then that does require a change for white box testing.
The good thing is after iteratively cleaning up code, you begin to see the bigger picture of you code and even more iterative cleaning up can be done, and so on. In the end, eventually, it is essentially a rewrite, but it is a longer, more sustainable and feasible process.
I have this exact conflict every time I open my projects. I'm perfectionist (because of being gifted and ADHD) so I always forget about the K.I.S.S. I'm creating an openworld, so I wanna have cleanest code possible and modular, but every time I open that project, I change same things again and again, because always there's a 'better' way to do them. I'm trying to be more realistic with my goals, but it's quiet difficult to let things 'flow', because I think I'll need something I haven't implemented yet, when actually I don't need it or won't make a big difference, and ends up adding to much overhead just for having it too modular. Logic is not my problem, but perfectionism is.
I used to be like you, currently I read Clean Code for every language I learn, then apply them if I can, and I think it's ok when I follow these rules (of some experienced developers, like Uncle Bob => you can try to read his book,...)
@@phamquangvi4413 I haven't said I write dirty code. logic, patterns and modularity aren't the problem. My problem is to stop refactoring code justo because there's a "better" solution, because my adhd makes me impulsive and makes me have low executive functionality like while making decitions or knowing when is too much. Because, as perfection isn't real, that's an infinite goal.
One of my ex bosses, the one that would do the tech stuff, would always shit on me because he didn’t like code that I would write. The problem was, when he writes code, lets say a web scraper, it literally took him months because he wanted it to look perfect, and I would get the same thing done in a weekend.
I think the advice is so simple that can be oversight how good it is. I can't recommend more the book of MartinFowler 'Refactoring:...' that the current edition is based on JavaScript. The key is not to think in a big complex design or elegant architecture, clean and beautiful. The magic really arise taking small steps, but there have to be automated tests, because when refactoring, and more on spaghetti code, always something can be broken.
“T’was hubris that led me here.” I’d like to think I learned a lot by trying to refactor code bases that I had no business trying to refactor, but, yeah, all in all, they were very modest improvements
As a software engineer, the method I use is simple: I refactor my code immediately after writing it. I consider refactoring as a part of implementing a new feature, and if the code I'm working on is not clean, my work is not finished.
There is usually a reason why spaghetti and 'unclean' code gets written, and while it is sometimes due to inexperience from the code, most often it is not. Spaghetti code is mostly a sign of mismatched design and poor understanding of the overall view features and requirements. Most of the resulting code look like band-aids and patches because of either an apparent difficulty to change more stuff than originally 'scoped', or a reluctance to break a design and go through a state of non-working code until you glue back the things you took apart. I found out that the me who just wrote spaghetti code (and knowing what I just did) is not well-equipped enough to be able to refactor the same code well. And when I tried, often I get stuck in refactor hell, creating new bugs, clashing against language features and dependent libraries, or coming to a roadblock in the design itself - resulting in a reversion of said refactoring process to previous build. Instead I found it to work best when me from six months later come back to review code from me six months ago. That is usually when I had enough of a breather to look through the design again, and when I learnt more about either the product, design patterns or even language features that I have in mind to be a good idea to use. This might be a good time to make worthwhile refactors over the things I need to change. Also, the 'spaghetti' code (which isn't really spaghetti by the time it passed review or made it to main branch) I wrote over the past six months served well as a decent visual to limit the scope of refactoring that I should do, so I don't have to hammer away the pieces I don't need to. A fairly good first step to pre-refactor is modularizing code - identifying common patterns in written code that can be split out to a specialized function. If the code appears only once, it is a decent signal to leave it alone. It probably does help that they were my personal projects. I got to control my development schedule, so I don't do refactoring in parallel branches with adding new code. If a refactor takes out more than a week, or more than 50% of the total code, that is not a 'refactor', that is a re-write, which is likely a signal to stop work anyway.
Spaghetti Code when you’re first learning how to use the language / framework -> Spend a couple weeks learning best practices / patterns, studying production grade codebases, and learning what works and what doesn’t work -> Rework the entire spaghetti codebase using your new found knowledge. When it comes to new projects you should never resort to spaghetti code unless you’re prototyping for an MVP. Production codebases are not a sandbox for your mistakes and laziness, only development.
I have a component with hundreds of lines in react. It's a table with 20 columnns... the columns have to be defined as children of the table itself. I could extract it to a separate file. I would get zero benefit from doing this...
An additional advice: if you can, and there are none, create some unit tests for the code you are refactoring. That way you can make sure nothing broke.
A good starting point for Clean Code in your projects is to use a code linter in your IDE + a code review tool that integrates in your CI/CD pipeline, such as SonarCloud. These won't avoid refactos but they surely will prevent common mistakes or bugs.
code review tools do not help. if you are a beginner, they will just make you rewrite your code in weird ways. if you are an expert, they hold you back.
i'm so glad to watch this video, because this sounds super similar to something I've done in the past too. Now I'm very cautious when it comes to committing to refactoring, and very cautious about updating / upgrading anything in the stack. I think we should only update is there's a bug in an older version, or there's a new feature we need in a newer version.
Add feature -> refactor, rinse and repeat. Eventually similar features are going to be easily addable, and only new features might need eventual refactoring
On a sidenote, I think there is a time and a place for a full refactoring. This could be when you want to eliminate a piece of the current tech stack, or when simply so much time is being eaten up by finding bugs and extra time needed to add features that the refactoring pays for itself in a relatively short amount of time.
Completely agree. Only refactor if you need to modify the code that was originally written. As long as the original code works, leave it. Pretty sure that Uncle Bob agrees.
Here are some guidelines I collected, in no particular order and by no means finished. Feel free to give some constructive criticism. ;-) * Do not produce tech debt in the first place. * Follow the Boyscout Rule (and leave the code you're working on cleaner than you found it). * Refactoring is not a phase or step, it is part of the actual coding. * Identify tech debt and plan its elimination like features or bug fixes. * Make them visible by putting them onto the Backlog and Sprint board together with other items. * Fix the most painful areas first. Apply the 80-20-Rule. * Use tooling wherever and whenever possible to avoid or eliminate tech debt (code analysis, refactoring etc.) * Perfection is the enemy of done. Find effective yet good enough solutions.
Iterative is the best refactoring indeed ;) I tend to think more of it as generalizing repetitive features... It's time to refactor only when you see more or less the that same things repeated over and over. So it's about generalizing the parts that can be generalized, as simple as first noticing a couple of 5-10 lines that look very simialr here and there, then making a generic funciton of all thoses cases, and so on. The full refactoring/generalizing pattern just emerges almost naturally with such methods, because it starts looking cleaner as things grow bigger (which is the opposite of what happens in most projects!), so the patterns become visible and observable. And then, as it's iterative, it's best to just refactor old rather stable and untouched code! So as these parts become cleaner and freeer of bugs (because of the tiny groupings of all like-lookign functions/codes), it's simple to pass the refactoring unoticed to these old stable parts (it's not a hard refactoring, it's just continual grouping of similar functions and behaviours), then the new parts can slowly be made part of the refactroed code, while also not having to refactor everything else all at once. The refactoring patterns are visible and understandable, the non-refactored parts are visible (and can be refactored when time arises, or when there is a need to work again on these parts of the code) and the new parts can follow the newer/evolving coding patterns. Refactoring everything all at once, is deemed to fail, because it's not tested as it progresses... Whereas if it's a grouping/generalizing of common behaviours and small code parts, then these small parts become better tested in all their cases as the refactoring is happening. Real refactoring is a matter of small improvements that slowly creep into the production code without anyone even noticing; while actually stabilizing the repeated and generalized behavious! This leads to a point where lots of stuff are very loosely coupled, and can be moved around while remaining very stable, because they were tested in so many use cases already. At that point only, can a strutural change happen that actually causes almost no bugs and can make everything look neater quite quickly :)
Interesting story. All I will say (if it helps someone) is to consider that this is legacy code (per Michael Feathers) and refactoring should be done within the scope of the new additions or bug fixes. Most of the time, the problem-areas in code will come up early and often and getting this wrangled and cleaned up will result in life gettting easier in later iterations of fixes/additions.
Actually this story shows again that a proper architecture helps create things well from the ground up. Of course it wont protect you from messy stuff, but at least the mess is just in some places instead of all over the place
My attitude is: do your job and go home. If a requirement arises that needs code to be refactored, or stuff to be removed, then do it. Else allow it. One day you'll leave anyway and it won't be your problem. The people above you have no idea how that system works, just that it has the features they expect. This is especially true of agency work where directors just want clients to be happy. Refactoring time is something you'll maybe find more of inside a product team.
I am working on refactoring very very much :) And it's good money spent - I first fast prototype a feature fast, discover ever usecase that we may want and every loophole, then when it works and when there is time I refactor that to nice readable and smaller code - at that process I sometimes see some ways of making it faster, or I pick some minor bugs.
Issues start, if there are people in your project who never refactor and work on a particular feature for a while. Then it basically ends up in "need to do a big overhaul or never touch it again".
1:59 I exactly know what you mean. I'm working as a front-end developer in a small R&D team and all those new devices, all new protocols and devices that come along with them... Those are just driving me crazy.
Refactoring a project is a big problem, more complex than it might seem at first glance. In most cases it can't be solved in one step. This problem accumulating within long time and solve it too takes time. Do refactor step by step. Think about bad code base like excess weight. To solve problem this first step is stop eat sugar! If you done it, that's good. Next step is fitness fifteen minuts per a day.
I understand the point but this is more of an explanation about a lack of planning and not an argument supporting spaghetti code. I've been the tech lead on a project where other developers consistently left the code they worked on far worse than before. One dev decided to do his own 'rogue' refactoring, ignored precedent, and introduced bugs that have been extremely difficult to replicate. His refactoring turned code that flowed through a pipeline into a set of actions that reach into a black box of globalized state control that is hard to trace the new bugs he introduced. This cost us 3 extra months of fixing these new bugs and we kept pushing the release date. Now, we have paused development and are a week or two into a project to refactor it back towards a 'pipeline'. He rushes through tickets because he's too focused on getting things done within the point estimation of each sprint. This is wrong. If a developer completes 20 points in a sprint, but creates 10 points in bug tickets that trickle in over time, how many points did he complete? I would argue it's NOT 10 and in many cases completed NEGATIVE points. Those 10 points in bug tickets may go to other developers who also create a small bug or can't complete the fix in one ticket, etc. This is why I cannot stand the concept of measuring developer productivity by measuring their points per sprint!
After 25+ years of professional coding : 1. Clean code has only one reason to exist : could this code evolve and is it testable ? All other criterions (readability...) are bonus but not mandatory 2. Clean API for classes and clean hierarchy for model objects. The inside of classes could be messy, it does not really matter since it's easy to refactor class after class 3. It's ok to have a messy code if it's because of optimizations (speed, memory load...) 4. Clean code is important for certains part (the business objects for example). Controllers and templates could be messy or copy-pasted it's not really important since it's constantly evolving and changing (generally, it's faster to delete and start over a new design for example)
Massive refactor is almost always a bad idea. Plus one should never attempt to refactor unless one has good set of unit/intergration test. As you said at the last: Leave the code a little better than you found it. I also use refactor as a way to understand code. If I come across a method/function that is like 100 lines or so long then that cause brain overload. I will start refactor the code into methods and name them well.
i only really spent time refactoring when there's major bug creeps up, or there's major additional feature need to be included, for the rest of the code, i have old principle, "if it works : dont touch it"
Clean code isn't inherently a bad thing, unlike the video title suggests (which I hate so much). This phenomenon (of rewriting software not leading to success) is a known problem, already told by Robert. C. Martin in his Clean Coders video series. Indeed the solution is to prevent spaghetti code from the start, so that the code base remains maintainable and extendable over time, which is what clean code is about.
If not for refactoring, rebuilding the same thing in new tech, and things like that, the majority of dev jobs could be fully eliminated. "Killer features" are few and far between and often make little to no impact anyway .
@@83hjf Exactly. The amount of leetcode pros going through all of that just to display what the database gives them and occasionally update a record 😂
I'm a perfectionist and struggled a lot with getting even one project finished because it needed to be perfect. Then I tried the exact opposite of what I was usually doing and worked quick and dirty and woosh my first version of a project was done within 2 days and about 6 hours of coding. I don't even think my code was half as bad as I thought it would be
recently one of the architects implemented a "clean architecture", DI and CQRS for a job that only needed to read a PDF file and parse it and spit the output into a table.
The idea is very cool and philosophical, applicable to not just programming. You wanna, say, fix your life, well, fix one bit a time. Make it 80% perfect.
My pro-tip after 25+ years of professional coding : Code first, think after. If the customer is happy with the proof of concept, then write rock-solid unit tests and then refactor. Because in many cases, you don't have a vision, neither the customer. You don't know how a business could evolve and good, thriving businesses are constantly evolving. I've seen so many death of projects because of over-engineering
I kind of looked at you like a lot of other youtubers that make courses for web development. but I have actually been doing the odin project and they link a lot of your videos. So I think after I finish TOP I think I will go check out some of your courses 😅
I've had a similar experience with an overhaul / rewrite where I planned on completely overhauling the entire app but only ever got to do the frontend because I switched teams. It sucks and the backend is to this day still a huge mess that now because of my overhaul is only usable with the new frontend through a hacky git submodules setup and lots of symlinks... On a more positive note, the ld frontend code was such an utter mess that we're still better off with the new version using workarounds to connect the backend than we ever were with the old frontend.
Depends on the type of refactoring. After 4 years as a Vue developer, I looked at components I wrote at the beginning and cringed a little. Reworking components using gained experience made my code base much better, as well as introducing previously unavailable features, such as composition API. It helped of course, that everything was modular..
Title is a bit misleading. Clean code, as in good design, good variable names, functions doing only one thing and so on, is what saves your projects in the long run. I totally agree with the advice in the video, never try having perfect code as a requirement before releasing anything. Then you will never reach your goal. It seems you interpret the term "clean code" as "perfect code" with the perfect structure or something. To me it means basically good, clean code, which you would expect from an experienced programmer. That is you follow the guidelines and common sense of an experienced developer.
I'm one of those people who wrote shitty foundational code for a startup. But I had 45 days to do it. And I did it, by myself. Then we got reinforcements and were able to refactor some of the crap I had to do. We don't like writing shit code but sometimes time is more important. In the end we had to create a new architecture... simply because the new "architect" didn't like the monolith i had created and thought it wouldn't scale. So we spent 9 months rewriting everything to sCaLE. The company crashed. Scalabiity was also a non issue because it was a delivery app used in TWO cities. It handled 1000 transactions a month...
@@83hjf your case seems different from what I was talking about. There were people hiring undergrads who have never worked on React to make the foundation for the whole project. And this project was huge and necessary to be scalable as it was intended for multiple manufacturing companies around the globe. Another one was a React app that they tried to make with pure css for some reason. They wanted it quick but also didn't want to use any animation libraries? As a result they hired some cheap interns to re-invent the wheel that senior devs have already made free packages for. You can guess how that turned out. There are many startups like this.
I can relate to this so much. Im a oerfectionist and i find myself constantly refactoring and even recreating the projects from scratch. Which is exhausting. I spend countless time trying to perfect the framework and its not productive at all. On the contrary, my boss prefers to just have apps work and keeps it simple. He gets from A to B far faster. I end up with a much more complete project with unnecessary features, tons more code, and a lot more time spent. I recommend people find more of a middleground.
What were you doing? Add some automated tests, break some dependencies, test and publish your code. Hours, maybe days per refactoring. Each step as more atomic tests, maybe an integration test or 6. If it's more than 20 hours of work you were buying of too much at a time.
Spaghetti code is the death of a project. After 40+ years as a software engineer (retired now) this has been my experience. And as Kyle found out taking over a project it is very difficult and refactoring is even more difficult. Clean code, small individual functions and remember the code should be readable by humans first. Kyle does great videos.
Y tf should I care about humans?
On other hand, some of the practices of the so Called Clean code book , when followed blindly instead of seeing where they apply reasonably, result in another type of spagheti (class bloat, that is just a different type of confusion)
@@jasperonandroid I am currently refactoring a 1200 lines function with a cyclomatic complexity of almost 180... this is really no fun.
The problem is that there is no clean code. I worked for almost every company from FAANG and clean code is not something I experienced.
I'm pretty new in software dev and it's one of the first things I've learned the hard way: Keep your concerns separate. You should always be able to completely replace one thing, and everything else still works. X goes in, Y goes out, the rest of the code doesn't care what happens in between.
That way, you never even get in the situation described in the video here, where you do a "refactor" that essentially amounts to a rewrite of the entire project. You shouldn't have to do a "refactored" branch that you work on in parallel to the productive branch. You should be able to push small, bit-by-bit refactoring to the productive branch.
I'm not very experienced so maybe I'm being naive here, but maybe the best way to deal with "spaghetti code" is to leave it alone while it works, and when you do have to get into it, cut it to pieces while you're at it. Let's say you' go into legacy code to fix a big function that does 3 things. Split it into 3 while you're there, if possible. It may not be necessary at the moment and it may not offer immediate benefits, but it very well may pay off the next time you or anyone has to go there. Even if the logic is just as messy as it was before, separating concerns can go a loooong way and make any future refactoring efforts so much easier.
Great advice! (20-year software engineer here). I'm surprised you didn't use the term "tech-debt", that's basically what you're describing. I think of addressing tech debt the same as addressing financial debt. You don't pay off 100% of your credit cards and then not pay your rent and buy food that month... You pay off a chunk at a time when you can afford it. If you address tech debt the same way (basically what you were describing) eventually your tech debt will be at a more reasonable level.
tech-debt is overloaded term, but it is used all the time, it almost seem tech is not moving, so you go back and pay the price, but in fact when you or your team have a chance to refactor the code, the tech you use could already be out-dated. because tech is moving fast :)
I was literally doing unnecessary refactoring this exact moment haha, what's so special about kyle is that he doesn't just teach you how to program, but the best, most optimal practices!
well, you can do test first unit testing to make things easier while you refactor. Saves you time on testing. Of course you have to be well verse on unit testing before jumping into it. When we refactor by batch, we strictly requires unit testing automation
Good advice. Refactoring without unit tests is pointless
Refactor as you go. Ideally, think about the problem you’re solving in an abstract enough way that future iterations don’t need to be refactored to be extended. If you’re about to put a band-aid over another band-aid, refactor that section.
I promise, most devs are not spending too much time refactoring. They are writing 90% spaghetti code.
Yes
I can't argue whether it's 90%, but most developers certainly don't go over their very first version of a function to at least clean it up.
Facts
You promise? Source?
That spaghetti code is their refactored code.
You didn't refactor the code, you did a rewrite. That's an important difference!
Exactly
Hell yes. I'm glad you mentioned this: a refactoring is an isolated micro change to improve the design of existing code. If it is taking hours, let alone days, weeks or months, that is something else entirely. You _may_ end up making substantial changes over a long period using thousands of small refactors, but the overall activity is no more "refactoring" than running a marathon could be called "taking a step".
Fully agree. And even if you do a large-scale refactor; do it in small steps. With your anecdote: a lot less time would have been wasted if you regularly merged the code into main and got to experience the benefits with implementing the new features. It also makes sure it is more in sync, you don't want a branch of a version that is a month old.
The only large-scale refactor I had to do was a case where our data model did not match the data model of the source. So when they had a new feature, we could not implement it. But that refactor had a lot of planning to break it down in small steps.
^ Ok bot
Clean and maintainable code always wins in the long run. You should always write well structured, understandable code.
The thing you don't need is extremely optimized code for performance, that can come later when you actually reach a bottleneck and improving performance becomes a goal. Oftentimes, the time you'd spend gaining a few extra cpu cycles wouldn't even be noticed by the consumer.
As for refactoring, you never have idle time to work on that. The way I do it, is I only refactor the classes and methods that I'm using directly in this new feature or patch that I'm working on. It's that idea of always leaving the place cleaner than when you entered. You do small (or large) refactorings along the way to achieve your goal, and the refactoring never becomes an activity in and of itself, and then you don't ever need to convince your managers and higher ups to alocate time for this (because they won't want to, they usually only care about the finished product).
Sure. If they only care about the finished product then they shouldn’t care how it’s been programmed either.
Also not creating a class for everything, not having documentation comments, not writing tests and using basic commands of the programming language rather than the latest hot shit which takes time to learn, allows you to finish the project in a quarter of the time allocated for it during scrum meetings.
That does not mean the code will be disorganised. It will be logical and will function.
If any extension to it is needed then the programmers should be able to work on it if they are indeed programmers.
if you put 5 developers in a room to develop an app. I bet they will all do it differently
Hopefully they use Prettier for common code formatting at least.
@@Blast-Forward 🤣
it's usually weird when I see a code that's poorly formatted; especially HTML, CSS or JS with 4 spaces as indentation.
@@jeremy0x4 spaces is not poor format
@@jeremy0x I do space as indentation but just while I'm tweaking. Having that breather can help you find the lines easier.
Stupid saying that perfectly outlines the lack of planning in this society and also the perfection of constantly finding excuses for responsibilities.
If you have a number of developers, the first thing to do is to design common standards and guidelines so that everyone does NOT do their own thing. Millions of projects show exactly this wrong behavior. incredibly ugly code in which everyone wanted to leave a little bit of their own "scent" because they consider themselves to be the best of all developers and in the end a project that triggers nausea and that no one can really work on properly.
Hi Kyle, I've been a software engineer for longer than I'd like to admit to 🤫 and I totally agree that the refactoring effort can be a pointless and expensive portion of some projects. I worked on a large banking project for a well known UK finance house and spent (along with my team) way too long on refactoring code only to add more issues and cost. I understand the technical reasons but sometimes 'less is more' - it's a cliche I know but doing nothing sometimes beats doing lots !! - love your videos and all the best from UK !
I believe it's ok to refactor when you just start coding in general.
I have made like 4 iterations of my user style sheet for RUclips, the first looks like a long list of single selectors with 1 or more properties, the second has grouped them together based on the visible feature, and finally uses more selectors in one given rule.
The third no longer groups them per feature, and puts the feature name in a comment before the selector, the fourth is below the third, calling it "restructure", and it mostly gave up on the feature names, is now grouping them based on the property, and only one property per rule. (unless it's a special rule in misc, then it can have more properties)
This is something I always have trouble balancing. We had some really bad developers in the past. I'm frequently refactoring their projects, removing 90% of unnecessary code, getting massive performance gains, and fixing a few corner case bugs. There's so much to fix though, and I need to work on other projects too.
oh, balancing refactoring legacy code can be a challenge, It's great that you're making progress by removing unnecessary code and improving performance. Prioritize the most critical issues and break down the work into manageable tasks. Remember, it's a gradual process, so don't feel overwhelmed. Keep up the good work.
Refactoring iteratively is excellent advice.
In your scenario, if you have new feature requests coming in and you don't know how much time you have available then you can't commit to a huge refactor.
I refactored constantly, but often it was just the code file I was working in and small and easy refactors to make the code easier to read and update. I did plenty of large refactors, but I always knew I had the time available to do those before I had to add new features.
Basically, if you're going to do a large refactor, then make sure the boss knows you need X weeks to just work on that.
Your 80/20 advice is also excellent. If you can easily read and understand what the code is doing and it reasonably efficient, then your time is better spent elsewhere.
What works for me is to put all the messy spaghetti code into a function, tuck it away in the 'utils' folder, and enjoy clean code in the rest of the project. It's like hiding the mess under the rug and never looking under it - very effective! 😄
Unfortunately sometimes you have to add spaghetti code in multiple files. Imagine if you have a system that adds stuff to a database, render an html page, sends email, sends notification and other bunch of stuff and each have their own file and your changes would break all of them unless you spaghetti code all these files.
@@soniablanche5672 Usually, such a problem is solved by organizing the code, isolating individual functional parts from each other, thus preventing them from disrupting the operation of other parts. Here, you may have to compromise on the DRY (Don't Repeat Yourself) principles, but in my opinion, it's better than getting errors in unexpected places.
You nearly got me with this one haha, overall great advice, using an iterative approach means that you can do small refactorings, and target areas of the code you are going to change next, I find that writing tests to capture the behaviour of your code before starting a refactoring is pretty much essential too, if you code doesn't have tests before you do this, it will after, this is also a good way to get into TDD
That's the role of a software architect, planning before coding is key in my opinion
Clean code is a natural by-product of good design and vice-versa.
"Refactor small bits at a time".
If you lookup what 'refactor' originally meant, it's always small, by definition.
People use it as a fancy way of saying 'rewrite' but that's not what it originally meant.
also instead of refactoring, time is better spent writing tests. THEN you can think of refactoring.
Refactoring is the process to generalize ideas, for example, putting common code inside parent classes or aggregating common strategies across the code.
A good refactoring pull request has more deleted lines than added lines. If there is more added lines, it's not a refactoring
Additional tip to tip #2 ( There is no perfect code):
Accept that some parts will be messy, but if you can: try to separate the messy/complex stuff from the other parts of the code as well as you can. This is where the single responsibility principle really shines. If you have a complex part that you simply can not simplify because it just is that complex, please make sure that the complexity doesn't leak out. Put it in it's own file(s) or module and make sure it's api is strict.
Very good advice. Sometimes, some code is messy because of optimization (speed, memory load...) or external components with messy API.
But if it's wrapped in a class with a clean API, it's ok. SRP is the first principle for avoiding spaghetti code.
The challenging part about refactoring is figuring out the domain model of the problem at hand. Most devs aren't good at this for some weird reason because we only test maths skills at university.
All this DRY stuff doesn't help much. If the inherent problem complexity requires 2 things to be almost the same they're still separate things. DRY makes it tempting to introduce coupling to things that shouldn't be coupled.
The "soft" part of software is actually important. You must preserve the ability to change the code. If you see a "utils" or "tools" or "misc" file, alarm bells should be ringing.
All in all, good info. I typically start with, plan, get it working, then look at flow and performance improvements, set a time limit for each part. Definitely set limits and add dev notes if you have ideas for the next iteration or next developer to consider. Smaller changes are usually more focused. If you have a team, peer review can also be helpful. There is also value in putting it down for a while and coming back to it.
I am experiencing something like what you went through when you were a fullstack developer. I do all the coding at my company (frontend with React and React Native, backend, deployment, etc.). I always want to refactor all the code because the me of two months ago was dumb. The worst thing is writing refactored code, which is supposed to be better, and it's not. I hate programming alone, but I think it's good in a certain way. Thanks for the tips! :)
In our project, we started refactoring by just hunting all if nestings and made them into if guards. It made us feel good about the code. The next time we refactor we'll focus on something small like that and change it across the code.
It all depends what a "clean code" means to you :)
For me clean code is just to follow the project rules, for example never put styling and logic mixed togther, DRY - move logic functions to separate files, sometimes just ask somebody else if your code is understandable. That is all you need for clean code.
> ask somebody else
Even further - honest reviews in pull requests. Don't accept my code just for the sake of it, if something bothers you just tell me. A lot of colleagues seem to really hate going through Pull Requests. I love them: Here I'm seeing other peoples approaches and techniques to learn from and get feedback when my own code might needs improvement. Even if a single colleague tells me they don't understand it for any ridiculous reason, that means I might be able to solve that already by an inline comment or just split a single function into two. Everyone's concerns are valid to me - even those of the dumb colleague I dislike, because starting a fight or proving them wrong doesn't let us progress as a team. :)
If you approach me and give me a hard time about DRY / Clean Code I'll refer you to KMA methodology..
my experience is that you need to find a good balance between refactor and progress. Also often times you can get better at designing new features in a clean way to begin with. Of course there will always be imperfections but just stick to the rules at least and clean up stuff when you touch the code again
Another big problem I have encountered is that programmers often tend to make stuff usable for every future undefined scenario. But they actually just dont know so should just stick to what they know right now. Often times they also reuse old stuff for new requirements and start implementing different modes inside a component for instance in angular. Its no shame to do a new component and make sure it does one job but one job good
A fellow developer taught me this concept in this word "Refactoring debt" and it instantly clicked.
When you go and implement a lot of features without refactoring old code as you go, it becomes an increasing debt. If you keep this going the debt will be too heavy to pay. So its better to refactor code as you go by doing small chunks each time.
It seems like if you're spending that much time to refactor the code, it seems like it wasnt "clean" in the first place, Open close principle for example suggest that its open to extend but not modify.
Being 1 developer on the project won't make you truly appreciate refactoring. Have you ever tried to work with a codebases supported by one hundred developers when no one refactor anything because the whole thing is already so fregile that jenga looks like a fortress next to it? I was and it's a complete nightmare. Refactoring should be embedded in the process on the most fundamental level and yes, it should be iterative, than it will work very good
I'm in the process of cleaning the code that's almost 20 years old. I've started when it was almost 19 years old. Fortunately, I don't have any deadlines and I'm almost done. According to the script that's checking the progress, I have around 500 elements in 77 files to go. I'm doing small changes at a time, so it won't be a full blown revolution, but rather an evolution into the right direction.
It depends. I think some "clean code" best practices just aren't worth the hassle like hardcore inheritance, annotations, getter and setters when they aren't needed. But some have in my opinion have a massive impact long term. The biggest one being exact typing in typescript. For example if you have a function that takes a string but in reality only does something useful with the strings 'yes', 'no' or 'maybe' than specify this with a union type. When you look at the function a year from now you are gonna be glad you did.
I find the Microsoft "interfaces" (not sure if other languages have something similar) to be hugely beneficial in keeping types honest across objects.
Absolutely, While some "clean code" practices may not always be worth the hassle, others, like exact typing in TypeScript, can have a significant long-term impact. Using union types to specify the valid input values for a function can improve code clarity and maintainability. It helps future developers understand the intended usage and prevents potential issues down the line. It's definitely a good practice to adopt for better code readability and maintainability.
Best tip me helped to write clean code was TDD.
Define what it should do, code don't have to be perfect, and can be optimized, and can have bugs.
But you define what it should do in the test, and everybody knows what it should do, and can make it work, or more readable.
So if you don't have time to make it work, you know already what it should do, and a new developer can make it work.
If you have bugs, add that scenario to the test, and then write the code.
Think about data, not about code. So what should be the input and output.
When you are the solo developer, your code is fine no matter how. But when you work in a team, spaghetti code is a big NO. It is not reusable, repeat yourself and how you do unit test?
This is a great topic and it sort of comes down to idealism versus pragmatism. Engineers often look at a problem and assume that they have a better solution than the current one, and frequently they do. But it is yet not realized, it's only theoretical. As such, it's impossible to know all of the unintended issues it may create, which in some cases completely offsets any benefit gained.
There are no perfect solutions.
We are in Project Business. So this means we have a very tough time schedule to deliver the final version of the project software.
In parallel we try to standardize our project software to be reused as a product after the project has finished. Now the tricky part. Every 3 month full of new feaure development we get the chance to do some imporvements in a couple of days (at most 5). But you are only allowed to do so, when you are not too late delivering your 3-months-potion of the whole project.
We are kind of screwed and are sitting in a big ball of mud at the moment.
I tried to group up developers to not implement new features when improvements are stacking up endlessly.
great job man, good to see you actually made a video for this. right on point. but I assume a lot of engineers might disagree, because large tech corps always tell their employees writing clean code and add more unit tests to be "engineering excellent". One thing most engineers do not even think about is those companies can afford their employees to write clean code and unit tests and continuous refactoring, and for sure clean code makes your peers or supervisors feel good when they look at it, how many times you've heard "remove these comment, it is not clean" when doing PR code reviews ? if you leave your job, they will hire another employee to do the same thing. Writing clean code is not a bad thing per say, but it will not do anything. it is simply a bundle of files. the product needs to reiterate, it needs to serve consumers, clean code will not do that. How many engineers knew those big tech firms at the time when there were only a few employees ?
This is exactly what is happening to me right now. I am alone working on a project for a ver small company and it's hard. I'm being perfectionist most of the time.
i'm happy with my code when people who don't much about programing or development can read it and have an idea of what im doing or better yet have an idea of where my problem could be coming from. or know where if they had to add code where it could go. all these things happened to me and it was pretty satisfying seeing people react to my code that way. i try to avoid going full C++ snob mode where you spent to much time trying to decode my code first before you can understand what its doing.
In my experience, it was usually better to forbid those people from changing critical code. Sometimes it isn't practical to make things understandable by everyone.
@@sexygeek8996
Fair enough, i understand where you're coming from.
I think this video compares apples to oranges. The first part talks about "the big refactoring", but it doesn't sound like a refactoring to me. It sounds more like rewriting at least a large chunk of the project.
To quote Wikipedia: "code refactoring is the process of restructuring existing computer code-changing the factoring-without changing its external behavior. Refactoring is intended to improve the design, structure, and/or implementation of the software (its non-functional attributes), while preserving its functionality." The fact that you're not changing the code functionality during refactoring is quite important. It means that your unit tests should continue to work after the refactoring.
Here is the process that I'm trying to follow regarding refactoring:
1. Don't start refactoring the code just for the sake of refactoring. Have a goal, like adding a new feature.
2. Identify the part that needs refactoring (if any) and what kind of refactoring does it need, to accommodate the new feature.
3. Make sure there are adequate unit tests for the relevant code. As a minimum you need tests that cover all the ways that code is currently used.
3a. If you're especially paranoid, and you should be, you can also see if the tests actually work by deliberately breaking the code to see if the tests will fail.
3b. Commit.
4. Do the refactoring. Run the tests. If they pass you can commit.
5. Implement the new feature. Write tests for it.
6. Was the refactoring actually good? Perhaps you found a better way to approach the whole thing? If so, throw away the code from point 4 onward. Go back to 3.
7. Go for code-review and merge in the main branch.
Of course this doesn't always work. For example, there are features that are just too big to do in one single cycle. For these you can use the Mikado method. Google it. I think it's great.
Another great use for refactoring is to help you read and understand legacy code[1]. In this case you don't even need the unit tests. You just change the code the way you think it should be and see what happens. When you're done playing, throw away all changes. You now have a better understanding of how the code works and perhaps why it's written the way it is. If you still think it should be refactored, you should go through the above procedure.
--
[1] "Legacy code" is the technical term for code written by somebody else or code that you've written more than 2 weeks ago.
You should slowly add unit tests too as you work on new/old code. I inherited a large code base last year that was written mostly bad. I started alone, but after a few months we hired one more dev. I got a good 25% of the code refactored to clean code. We’re now at about 50%. It takes time and patience, but unit tests are a must. If you refactor and then you introduce regression, you want to catch it as soon as possible. Sometimes they slip through, but it’s worse without any tests. Documenting is also a must, so you have use case steps to follow to ensure it’s all good
Agree. Unit tests will also help you structure the logic, make sure you explicitly know what context you're in and make sure the methods are not too complex because then it becomes difficult to test. Also, write better assertions as you go if using typescript. Because that will jump at you even before the unit tests.
What do you do when you have existing tests that are inherently coupled to the existing implementation? Tests are not unit tests and don't test the API.
@@majorhumbert676 depends on what you’re testing. Black box testing will *always* be coupled to the implementation. White box testing shouldn’t be. Although if a function changes the post outcomes then that does require a change for white box testing.
The good thing is after iteratively cleaning up code, you begin to see the bigger picture of you code and even more iterative cleaning up can be done, and so on. In the end, eventually, it is essentially a rewrite, but it is a longer, more sustainable and feasible process.
I have this exact conflict every time I open my projects. I'm perfectionist (because of being gifted and ADHD) so I always forget about the K.I.S.S.
I'm creating an openworld, so I wanna have cleanest code possible and modular, but every time I open that project, I change same things again and again, because always there's a 'better' way to do them.
I'm trying to be more realistic with my goals, but it's quiet difficult to let things 'flow', because I think I'll need something I haven't implemented yet, when actually I don't need it or won't make a big difference, and ends up adding to much overhead just for having it too modular.
Logic is not my problem, but perfectionism is.
Jesus, I'm the same way, and definitely attribute it to my ADHD as well. It's like I know I'm doing too much, but it's a struggle to pull back.
I used to be like you, currently I read Clean Code for every language I learn, then apply them if I can, and I think it's ok when I follow these rules (of some experienced developers, like Uncle Bob => you can try to read his book,...)
The main idea is you just apply these clean code rules if you actually need them for now, not for the future
@@phamquangvi4413 I haven't said I write dirty code. logic, patterns and modularity aren't the problem. My problem is to stop refactoring code justo because there's a "better" solution, because my adhd makes me impulsive and makes me have low executive functionality like while making decitions or knowing when is too much. Because, as perfection isn't real, that's an infinite goal.
@@joacotossello ok, I got it :D
One of my ex bosses, the one that would do the tech stuff, would always shit on me because he didn’t like code that I would write. The problem was, when he writes code, lets say a web scraper, it literally took him months because he wanted it to look perfect, and I would get the same thing done in a weekend.
I think the advice is so simple that can be oversight how good it is. I can't recommend more the book of MartinFowler 'Refactoring:...' that the current edition is based on JavaScript. The key is not to think in a big complex design or elegant architecture, clean and beautiful. The magic really arise taking small steps, but there have to be automated tests, because when refactoring, and more on spaghetti code, always something can be broken.
“T’was hubris that led me here.”
I’d like to think I learned a lot by trying to refactor code bases that I had no business trying to refactor, but, yeah, all in all, they were very modest improvements
I am experiencing this right now with a new company I just joined 😂. I’m presently refactoring
Thank you for bringing this up! I saw a lot senior developers wasted a lot budget and the product couldn’t never get out !
As a software engineer, the method I use is simple: I refactor my code immediately after writing it. I consider refactoring as a part of implementing a new feature, and if the code I'm working on is not clean, my work is not finished.
There is usually a reason why spaghetti and 'unclean' code gets written, and while it is sometimes due to inexperience from the code, most often it is not. Spaghetti code is mostly a sign of mismatched design and poor understanding of the overall view features and requirements. Most of the resulting code look like band-aids and patches because of either an apparent difficulty to change more stuff than originally 'scoped', or a reluctance to break a design and go through a state of non-working code until you glue back the things you took apart.
I found out that the me who just wrote spaghetti code (and knowing what I just did) is not well-equipped enough to be able to refactor the same code well. And when I tried, often I get stuck in refactor hell, creating new bugs, clashing against language features and dependent libraries, or coming to a roadblock in the design itself - resulting in a reversion of said refactoring process to previous build.
Instead I found it to work best when me from six months later come back to review code from me six months ago. That is usually when I had enough of a breather to look through the design again, and when I learnt more about either the product, design patterns or even language features that I have in mind to be a good idea to use. This might be a good time to make worthwhile refactors over the things I need to change. Also, the 'spaghetti' code (which isn't really spaghetti by the time it passed review or made it to main branch) I wrote over the past six months served well as a decent visual to limit the scope of refactoring that I should do, so I don't have to hammer away the pieces I don't need to.
A fairly good first step to pre-refactor is modularizing code - identifying common patterns in written code that can be split out to a specialized function. If the code appears only once, it is a decent signal to leave it alone.
It probably does help that they were my personal projects. I got to control my development schedule, so I don't do refactoring in parallel branches with adding new code. If a refactor takes out more than a week, or more than 50% of the total code, that is not a 'refactor', that is a re-write, which is likely a signal to stop work anyway.
Spaghetti Code when you’re first learning how to use the language / framework -> Spend a couple weeks learning best practices / patterns, studying production grade codebases, and learning what works and what doesn’t work -> Rework the entire spaghetti codebase using your new found knowledge.
When it comes to new projects you should never resort to spaghetti code unless you’re prototyping for an MVP. Production codebases are not a sandbox for your mistakes and laziness, only development.
Now I feel better with having over 1000 lines for one single React component.
That's an exception for clean code rule. 1000 lines of code for one component is way too many 😢
That's too bad
It will come biting you one day
Those are rookie numbers in this racket
I have a component with hundreds of lines in react. It's a table with 20 columnns... the columns have to be defined as children of the table itself. I could extract it to a separate file. I would get zero benefit from doing this...
An additional advice: if you can, and there are none, create some unit tests for the code you are refactoring. That way you can make sure nothing broke.
A good starting point for Clean Code in your projects is to use a code linter in your IDE + a code review tool that integrates in your CI/CD pipeline, such as SonarCloud. These won't avoid refactos but they surely will prevent common mistakes or bugs.
code review tools do not help. if you are a beginner, they will just make you rewrite your code in weird ways. if you are an expert, they hold you back.
i'm so glad to watch this video, because this sounds super similar to something I've done in the past too. Now I'm very cautious when it comes to committing to refactoring, and very cautious about updating / upgrading anything in the stack. I think we should only update is there's a bug in an older version, or there's a new feature we need in a newer version.
making the code 1% more readable is already considered "refactoring". Never ever recode/refactor everything at once.
well it's situational. Sometimes you have to.
Add feature -> refactor, rinse and repeat.
Eventually similar features are going to be easily addable, and only new features might need eventual refactoring
On a sidenote, I think there is a time and a place for a full refactoring. This could be when you want to eliminate a piece of the current tech stack, or when simply so much time is being eaten up by finding bugs and extra time needed to add features that the refactoring pays for itself in a relatively short amount of time.
Completely agree. Only refactor if you need to modify the code that was originally written. As long as the original code works, leave it. Pretty sure that Uncle Bob agrees.
Here are some guidelines I collected, in no particular order and by no means finished. Feel free to give some constructive criticism. ;-)
* Do not produce tech debt in the first place.
* Follow the Boyscout Rule (and leave the code you're working on cleaner than you found it).
* Refactoring is not a phase or step, it is part of the actual coding.
* Identify tech debt and plan its elimination like features or bug fixes.
* Make them visible by putting them onto the Backlog and Sprint board together with other items.
* Fix the most painful areas first. Apply the 80-20-Rule.
* Use tooling wherever and whenever possible to avoid or eliminate tech debt (code analysis, refactoring etc.)
* Perfection is the enemy of done. Find effective yet good enough solutions.
Iterative is the best refactoring indeed ;) I tend to think more of it as generalizing repetitive features... It's time to refactor only when you see more or less the that same things repeated over and over. So it's about generalizing the parts that can be generalized, as simple as first noticing a couple of 5-10 lines that look very simialr here and there, then making a generic funciton of all thoses cases, and so on. The full refactoring/generalizing pattern just emerges almost naturally with such methods, because it starts looking cleaner as things grow bigger (which is the opposite of what happens in most projects!), so the patterns become visible and observable. And then, as it's iterative, it's best to just refactor old rather stable and untouched code! So as these parts become cleaner and freeer of bugs (because of the tiny groupings of all like-lookign functions/codes), it's simple to pass the refactoring unoticed to these old stable parts (it's not a hard refactoring, it's just continual grouping of similar functions and behaviours), then the new parts can slowly be made part of the refactroed code, while also not having to refactor everything else all at once. The refactoring patterns are visible and understandable, the non-refactored parts are visible (and can be refactored when time arises, or when there is a need to work again on these parts of the code) and the new parts can follow the newer/evolving coding patterns. Refactoring everything all at once, is deemed to fail, because it's not tested as it progresses... Whereas if it's a grouping/generalizing of common behaviours and small code parts, then these small parts become better tested in all their cases as the refactoring is happening.
Real refactoring is a matter of small improvements that slowly creep into the production code without anyone even noticing; while actually stabilizing the repeated and generalized behavious!
This leads to a point where lots of stuff are very loosely coupled, and can be moved around while remaining very stable, because they were tested in so many use cases already. At that point only, can a strutural change happen that actually causes almost no bugs and can make everything look neater quite quickly :)
Thanks for the information 👍
Interesting story. All I will say (if it helps someone) is to consider that this is legacy code (per Michael Feathers) and refactoring should be done within the scope of the new additions or bug fixes. Most of the time, the problem-areas in code will come up early and often and getting this wrangled and cleaned up will result in life gettting easier in later iterations of fixes/additions.
Uncle bob, kent beck and martin fowler disagree. Now u can choose between 3 monsters os software development or this guy.
Actually this story shows again that a proper architecture helps create things well from the ground up. Of course it wont protect you from messy stuff, but at least the mess is just in some places instead of all over the place
One man's refactored code is another man's spaghetti code and vice versa.
Nope
A good refactoring follow the DRY principle.
My attitude is: do your job and go home. If a requirement arises that needs code to be refactored, or stuff to be removed, then do it. Else allow it. One day you'll leave anyway and it won't be your problem. The people above you have no idea how that system works, just that it has the features they expect. This is especially true of agency work where directors just want clients to be happy. Refactoring time is something you'll maybe find more of inside a product team.
I am working on refactoring very very much :) And it's good money spent - I first fast prototype a feature fast, discover ever usecase that we may want and every loophole, then when it works and when there is time I refactor that to nice readable and smaller code - at that process I sometimes see some ways of making it faster, or I pick some minor bugs.
Issues start, if there are people in your project who never refactor and work on a particular feature for a while. Then it basically ends up in "need to do a big overhaul or never touch it again".
I think you just prevented me from making a huge mistake. Thank you.
1:59
I exactly know what you mean. I'm working as a front-end developer in a small R&D team and all those new devices, all new protocols and devices that come along with them... Those are just driving me crazy.
Refactoring a project is a big problem, more complex than it might seem at first glance. In most cases it can't be solved in one step. This problem accumulating within long time and solve it too takes time.
Do refactor step by step. Think about bad code base like excess weight. To solve problem this first step is stop eat sugar! If you done it, that's good. Next step is fitness fifteen minuts per a day.
Thank for sharing. That’s what i’m doing for all my projects. Sometimes, i was busy to review member code. That’s a big mistake !!!
I understand the point but this is more of an explanation about a lack of planning and not an argument supporting spaghetti code. I've been the tech lead on a project where other developers consistently left the code they worked on far worse than before.
One dev decided to do his own 'rogue' refactoring, ignored precedent, and introduced bugs that have been extremely difficult to replicate. His refactoring turned code that flowed through a pipeline into a set of actions that reach into a black box of globalized state control that is hard to trace the new bugs he introduced. This cost us 3 extra months of fixing these new bugs and we kept pushing the release date. Now, we have paused development and are a week or two into a project to refactor it back towards a 'pipeline'.
He rushes through tickets because he's too focused on getting things done within the point estimation of each sprint. This is wrong. If a developer completes 20 points in a sprint, but creates 10 points in bug tickets that trickle in over time, how many points did he complete? I would argue it's NOT 10 and in many cases completed NEGATIVE points. Those 10 points in bug tickets may go to other developers who also create a small bug or can't complete the fix in one ticket, etc.
This is why I cannot stand the concept of measuring developer productivity by measuring their points per sprint!
After 25+ years of professional coding :
1. Clean code has only one reason to exist : could this code evolve and is it testable ? All other criterions (readability...) are bonus but not mandatory
2. Clean API for classes and clean hierarchy for model objects. The inside of classes could be messy, it does not really matter since it's easy to refactor class after class
3. It's ok to have a messy code if it's because of optimizations (speed, memory load...)
4. Clean code is important for certains part (the business objects for example). Controllers and templates could be messy or copy-pasted it's not really important since it's constantly evolving and changing (generally, it's faster to delete and start over a new design for example)
You should make a podcast with this kind of information .it will be awesome
Massive refactor is almost always a bad idea. Plus one should never attempt to refactor unless one has good set of unit/intergration test. As you said at the last: Leave the code a little better than you found it.
I also use refactor as a way to understand code. If I come across a method/function that is like 100 lines or so long then that cause brain overload. I will start refactor the code into methods and name them well.
i only really spent time refactoring when there's major bug creeps up, or there's major additional feature need to be included, for the rest of the code, i have old principle, "if it works : dont touch it"
Not too long ago I did a really big refactor, but I regularly merged and deployed the new code.
The most important metric is how easy the code is to refactor. And doing it often.
I was doing the same a month ago but learned the hard way. I only refactor the component I'm working on
Clean code isn't inherently a bad thing, unlike the video title suggests (which I hate so much). This phenomenon (of rewriting software not leading to success) is a known problem, already told by Robert. C. Martin in his Clean Coders video series. Indeed the solution is to prevent spaghetti code from the start, so that the code base remains maintainable and extendable over time, which is what clean code is about.
you should speak about this experience with your psychologist) I was listening and feeling your pain)
If not for refactoring, rebuilding the same thing in new tech, and things like that, the majority of dev jobs could be fully eliminated. "Killer features" are few and far between and often make little to no impact anyway .
everything is a CRUD
@@83hjf Exactly. The amount of leetcode pros going through all of that just to display what the database gives them and occasionally update a record 😂
Thank you and this is why I love you.
I'm a perfectionist and struggled a lot with getting even one project finished because it needed to be perfect. Then I tried the exact opposite of what I was usually doing and worked quick and dirty and woosh my first version of a project was done within 2 days and about 6 hours of coding. I don't even think my code was half as bad as I thought it would be
recently one of the architects implemented a "clean architecture", DI and CQRS for a job that only needed to read a PDF file and parse it and spit the output into a table.
@@83hjf The very definition of "over engineering"! 🙄
The idea is very cool and philosophical, applicable to not just programming.
You wanna, say, fix your life, well, fix one bit a time. Make it 80% perfect.
My pro tip would be to THINK BEFORE YOU WRITE. Have a vision. Plan, then code, then adjust
My pro-tip after 25+ years of professional coding :
Code first, think after. If the customer is happy with the proof of concept, then write rock-solid unit tests and then refactor.
Because in many cases, you don't have a vision, neither the customer. You don't know how a business could evolve and good, thriving businesses are constantly evolving.
I've seen so many death of projects because of over-engineering
I kind of looked at you like a lot of other youtubers that make courses for web development. but I have actually been doing the odin project and they link a lot of your videos. So I think after I finish TOP I think I will go check out some of your courses 😅
I've had a similar experience with an overhaul / rewrite where I planned on completely overhauling the entire app but only ever got to do the frontend because I switched teams. It sucks and the backend is to this day still a huge mess that now because of my overhaul is only usable with the new frontend through a hacky git submodules setup and lots of symlinks... On a more positive note, the ld frontend code was such an utter mess that we're still better off with the new version using workarounds to connect the backend than we ever were with the old frontend.
Depends on the type of refactoring. After 4 years as a Vue developer, I looked at components I wrote at the beginning and cringed a little. Reworking components using gained experience made my code base much better, as well as introducing previously unavailable features, such as composition API. It helped of course, that everything was modular..
Title is a bit misleading. Clean code, as in good design, good variable names, functions doing only one thing and so on, is what saves your projects in the long run.
I totally agree with the advice in the video, never try having perfect code as a requirement before releasing anything. Then you will never reach your goal.
It seems you interpret the term "clean code" as "perfect code" with the perfect structure or something.
To me it means basically good, clean code, which you would expect from an experienced programmer. That is you follow the guidelines and common sense of an experienced developer.
Story of my life. That's how it's been in every startup I worked at. People write shitty foundational code and leave and we are left to maintain it.
I'm one of those people who wrote shitty foundational code for a startup. But I had 45 days to do it. And I did it, by myself. Then we got reinforcements and were able to refactor some of the crap I had to do. We don't like writing shit code but sometimes time is more important. In the end we had to create a new architecture... simply because the new "architect" didn't like the monolith i had created and thought it wouldn't scale. So we spent 9 months rewriting everything to sCaLE. The company crashed. Scalabiity was also a non issue because it was a delivery app used in TWO cities. It handled 1000 transactions a month...
@@83hjf your case seems different from what I was talking about. There were people hiring undergrads who have never worked on React to make the foundation for the whole project. And this project was huge and necessary to be scalable as it was intended for multiple manufacturing companies around the globe.
Another one was a React app that they tried to make with pure css for some reason. They wanted it quick but also didn't want to use any animation libraries? As a result they hired some cheap interns to re-invent the wheel that senior devs have already made free packages for. You can guess how that turned out.
There are many startups like this.
@@83hjfthat's why you shouldn't work for companies
I can relate to this so much. Im a oerfectionist and i find myself constantly refactoring and even recreating the projects from scratch. Which is exhausting. I spend countless time trying to perfect the framework and its not productive at all. On the contrary, my boss prefers to just have apps work and keeps it simple. He gets from A to B far faster. I end up with a much more complete project with unnecessary features, tons more code, and a lot more time spent. I recommend people find more of a middleground.
That typo in the word perfectionist is just too ironic.
“Refactor small bits at a time” - That’s exactly what I’ve been saying.
thanks for ur advices! I'm waiting for ur gsap tutorial 🥰🥰🥰
I spent last week refactoring my side project that is not even published yet. :)
Been there multiple times before. My learning has been it's an effort vs reward tradeoff.
I think the correct title for this video is: All or nothing refactors are stupid, I learned the hard way
What were you doing? Add some automated tests, break some dependencies, test and publish your code. Hours, maybe days per refactoring. Each step as more atomic tests, maybe an integration test or 6. If it's more than 20 hours of work you were buying of too much at a time.
Haha this video came out the moment I finish my one-month refactor.