Software Design Patterns

Refactoring for Rails - Using Deodorant to Prevent Code Smells

Refactoring for Rails - Using Deodorant to Prevent Code Smells

by Rodrigo Urubatan

In this presentation titled "Refactoring for Rails - Using Deodorant to Prevent Code Smells," Rodrigo Urubatan discusses the importance of refactoring in Ruby development during the RubyConfTH 2022 held in Bangkok, Thailand. He emphasizes that refactoring is essential for maintaining code quality and enhancing developers' efficiency.

Key Points Discussed:
- Refactoring Principles: Urubatan introduces refactoring concepts based on Martin Fowler’s book "Refactoring" and emphasizes the need to recognize when and how to refactor code to prevent issues commonly referred to as 'code smells'.
- Common Code Smells: He discusses examples of code smells, such as the absence of tests, complex conditional statements, and the presence of large, cumbersome classes or methods.
- Good Practices for Refactoring:

- Ensure all tests pass after refactoring.

- Allocate time for refactoring in daily work.

- Keep pull requests focused; tackle refactoring separately from feature development to improve review clarity.
- Refactoring Techniques: Urubatan explains useful refactoring techniques such as extracting methods, consolidating functionality into modules, and properly structuring classes to enhance readability and maintainability. He recommends using plugins for text editors like VIM and Sublime Text for efficiency in performing these tasks.
- Tools for Improvement: Tools like RoboCop are suggested to automate code quality checks before committing code, allowing for quick fixes and ensuring compliance with programming standards.
- Real-World Example: He shares a case study from his company where failure to update test cases led to issues after adding a new tag type, illustrating the potential consequences of neglecting test coverage during refactoring.
- General Advice: The speaker advises developers to be cautious about over-complicated and bloated classes or methods, stressing the importance of clear organization and simplicity in codebase structure.

Main Takeaways:
- Regular refactoring leads to cleaner code, fewer bugs, and ultimately more free time for developers.
- Integrating refactoring into the development process is crucial to maintaining a healthy codebase.
- Each iteration should include dedicated time for refactoring to avoid technical debt accumulation.
- Leveraging tools and good practices helps prevent code quality issues that could lead to increased future work or complications in development.

00:00:00.120 foreign
00:00:15.000 Good afternoon, everyone.
00:00:17.180 After starting to work with Ruby, one of my main objectives in life is to help people write better code and have more free time.
00:00:22.560 With that in mind, we need to take care of our code.
00:00:25.199 There is no magic trick to achieve this.
00:00:29.820 One of the best books I've read on the subject is 'Refactoring' by Martin Fowler.
00:00:34.260 It's not Ruby-specific, but after I read it, I decided to talk about it for the Ruby community.
00:00:36.360 There are many different refactoring patterns across various languages.
00:00:39.780 Sometimes, people refer to them by different names.
00:00:44.700 I prefer the names from Martin Fowler's book.
00:00:45.480 These names make sense in many languages, not just Ruby.
00:00:48.000 For example, you can change a function declaration, or collapse your class hierarchy.
00:00:51.440 If you have a superclass that shouldn't be a superclass of a certain class, you can modify that.
00:00:55.440 You can also extract a module, combining many functions into a module.
00:00:58.920 In Ruby, you can combine a lot of functions into a large class or decompose a conditional.
00:01:01.980 The names of these practices are pretty self-explanatory.
00:01:05.519 However, there is one type of refactoring I find challenging: the extract method.
00:01:09.320 I always feel the need to use an IDE, which I don't usually have when working with Ruby.
00:01:15.960 But I've discovered there's a plugin for VIM and one for Sublime Text that help with this.
00:01:20.420 The VIM plugin is much better, but both work.
00:01:25.860 Besides that, you basically copy, paste, and change variables, and that’s it.
00:01:29.280 For most refactoring tasks, this is sufficient.
00:01:31.140 But the challenging part is remembering when to do things and identifying problems.
00:01:35.640 People began using the term 'code smells' a few years ago to help identify issues in our code.
00:01:39.060 I think this idea is quite useful.
00:01:42.479 Let's start with this analogy.
00:01:44.700 Imagine visiting a marvelous country with beautiful beaches, but then you look at your code.
00:01:47.580 You check a new codebase and see there are no tests, leading you to think the original developers loved adventure.
00:01:51.840 Something is likely to break down the line.
00:01:55.319 There are lesser risks, like a simple case statement without an 'else', which can lead to code breaking in six months.
00:02:01.019 I have an example from my company where we had an enumeration for defining tag types.
00:02:06.840 All the current values were included in a case statement describing them with human-friendly names.
00:02:11.879 Six months later, when a reporter asked for a new tag type, we added an item to the DNM.
00:02:15.420 However, nobody remembered to increase the test cases to account for that.
00:02:19.200 As a result, we ended up with many unknown and new values exploding because there was no 'else' in the case.
00:02:21.920 So, that's one example.
00:02:28.260 Just a quick comment: if there are no tests, stop thinking about refactoring and focus on writing tests.
00:02:30.780 You won't be able to do anything effectively without tests.
00:02:33.420 To start, I want to list a few good practices for refactoring.
00:02:40.200 After completing refactoring, all your tests should still pass; otherwise, you may have broken something.
00:02:45.979 Refactoring, by definition, should not change the behavior of your code; you are improving it while keeping everything working as before.
00:02:50.700 In non-critical scenarios, you might change a class exposed as an API, but that’s not the ideal case.
00:02:55.800 Refactoring is about maintaining code quality, so you should allocate time for it in your daily work.
00:03:03.000 Each iteration, you should separate time for refactoring; if you take too long to improve your code, it will cost you much more later.
00:03:09.700 The time saved by improving code quality will also save your vacation time and reduce the number of bugs.
00:03:16.300 Since everyone likes to take time off, it makes sense to take care of your code.
00:03:20.740 As a golden rule, anything that doesn't simply improve the code should be handled in its own pull request.
00:03:23.340 If your code isn't properly organized, the people reviewing it will find it difficult to go through.
00:03:26.280 Instead of reviewing a hundred lines of feature-related code, they will review five thousand changed lines.
00:03:30.300 It's important to handle small refactorings always and leave the code you are touching in a better state than you found it.
00:03:35.979 Some problems seem simple but can lead to big issues.
00:03:40.000 For example, broken chains of 'safe' calls in Ruby may work, but they can break over time.
00:03:42.240 Similarly, a chain of three or four ternary operators might indicate that you missed one of the outcomes.
00:03:48.120 In general, this makes the code vulnerable to breaking.
00:03:53.579 Additionally, look out for things that don’t feel right.
00:03:57.999 This image references a show from a few years ago, illustrating how unexpected variables can appear.
00:04:00.300 If you encounter chemical case variables in your code, they probably don’t belong there.
00:04:05.400 For the first scenarios, several tools can help you address these issues.
00:04:10.000 For example, I suggest running RoboCop before you commit your code.
00:04:14.520 RoboCop verifies your code and can automatically handle quick fixes.
00:04:17.400 If you're using JavaScript, you can use 'lint-staged' to run it automatically before committing.
00:04:22.080 There are other code smells that are tolerable in an Italian restaurant but not in your code.
00:04:27.900 If your code looks like spaghetti, you really need to stop and fix something before it breaks.
00:04:31.500 Spaghetti code is usually problematic and doesn't look clean; it's often difficult to follow how it works.
00:04:36.000 In the best-case scenario, you'll notice that some classes are managing too many responsibilities.
00:04:39.000 When you have methods that are too large and do too many things, it’s time to refactor.
00:04:43.680 You may also face incomplete class libraries that could lead to circular dependencies.
00:04:48.180 Often the changes you make in one part of the code lead to unexpected breaks elsewhere.
00:04:52.740 This often resembles the IMAX code example where there's a warning for modifying code.
00:04:56.680 If you think you know what you're doing in such areas, beware and leave them alone.
00:05:01.200 These are examples of very well tested spaghetti code.
00:05:05.240 A significant problem associated with spaghetti code is the presence of 'whale classes'.
00:05:07.440 These are usually large classes with hundreds or even thousands of lines of code.
00:05:10.500 Such classes often indicate that they are performing too many functions.
00:05:14.160 Additionally, you may encounter 'doughnut methods' that are significant but not as big.
00:05:17.880 They possess high cyclomatic complexity and most of the time, they can be simplified.
00:05:21.060 For whale classes, if you identify multiple that have shared code, it’s possible to extract a superclass.
00:05:26.160 You can share that superclass across multiple classes or collapse the hierarchy to improve clarity.
00:05:31.200 If the class still feels like it should be doing more, consider combining functions into a module.
00:05:35.700 This will help isolate tests and keep your code cleaner and easier to read.
00:05:40.020 You can also move methods from larger classes to the new superclass or module.
00:05:44.760 Extract methods to reduce the size and complexity of the doughnut methods.
00:05:49.620 Turning variables into arguments simplifies code and helps to share code among methods.
00:05:54.000 This often occurs when the methods have small different variations.
00:05:56.760 To further improve readability, you can rename methods and variables for better clarity.
00:06:01.140 Sometimes, your code might feel like a fruit salad—lacking coherence.
00:06:05.940 A class with too many instance variables can lead to confusion.
00:06:10.000 If changing one variable breaks another property, that’s a red flag.
00:06:13.620 A class with too many methods can usually be solved by extracting a module.
00:06:16.920 You can simplify methods with excessive arguments by creating value objects.
00:06:20.760 Alternatively, you can pass a hash with possible values to the method.
00:06:24.480 This facilitates testing individual cases and achieves a cleaner layout.
00:06:30.360 Moreover, examining your code should not feel claustrophobic or overly complicated.
00:06:35.820 There are aspects in code that feel out of place.
00:06:37.740 For instance, unneeded comments like 'this code removes an item' can be misleading.
00:06:42.600 Instead, aim to explain the business rationale for the method's existence.
00:06:47.520 If you find yourself needing to explain exactly what a method does, reconsider the naming.
00:06:53.760 Comments that explain the business rationale are extremely useful.
00:06:56.880 I've encountered unnecessary libraries.
00:06:58.920 Once, I found an npm module that compared a value to itself—it seemed like a joke.
00:07:02.800 However, such modules unfortunately proliferate.
00:07:05.280 It's crucial to address situations where a method has multiple return statements.
00:07:08.400 Having excessive return points within a method leads to confusion and potential errors.
00:07:13.860 If you see numerous exit points in a method, consider breaking it into smaller functions.
00:07:17.400 Improving logic by using early conditions can also streamline your code.
00:07:22.020 This should result in simpler, more linear logic.
00:07:25.740 As I mentioned earlier, creating value objects in Ruby is infrequent.
00:07:29.340 You can often use a simple hash or create a struct.
00:07:31.320 You don't need to define a class with many accessors for variables.
00:07:35.340 structs create classes that grant basic functionality without additional overhead.
00:07:40.680 All these extraneous elements should simply be removed from your code.
00:07:44.980 You will notice areas of code that induce a sense of claustrophobia.
00:07:48.060 You feel it should be easier to make changes.
00:07:51.100 This tension arises frequently in spaghetti code or large classes.
00:07:56.160 For instance, having a single user class duplicated across multiple applications leads to complications.
00:08:00.140 Every modification necessitates updating all instances, which is impractical.
00:08:05.080 Encapsulating such shared logic into a separate service is usually a better approach.
00:08:10.980 You may find yourself needing to change various points in the code.
00:08:13.420 This increases complexity and can generate more problems than it resolves.
00:08:17.920 Next, let's discuss classic error pointers, such as fat controllers and thin models.
00:08:22.760 When your controllers are bloated with database-related code, it's a sign of trouble.
00:08:26.700 If a controller method has more than 50 or 100 lines, refactoring is necessary.
00:08:31.560 Controllers should delegate responsibilities to models, which also should not become bloated.
00:08:35.820 For instance, if you have a song model, it should only define its core attributes.
00:08:39.960 If it starts to include file conversion methods, that indicates a design flaw.
00:08:44.460 Such functionality should reside in separate services or libraries.
00:08:48.700 Fat models violate the principles of the Rails framework.
00:08:52.600 Ideally, all database-related interactions should occur within the models.
00:08:56.560 Controllers are meant to pass parameters into models and retrieve data for views.
00:09:00.280 This streamlined approach enhances testability and delineates responsibilities.
00:09:04.800 Moreover, certain elements may simply feel wrong or raise red flags.
00:09:09.000 For example, if modules are used in Rails migrations.
00:09:12.839 This creates complications should you need to reapply migrations across different databases.
00:09:16.680 I’ve faced this problem multiple times.
00:09:20.420 Migrations missing a down method complicate rollbacks.
00:09:23.520 Without the ability to revert migrations, you can’t update effectively.
00:09:27.179 These are clear indicators that problems will arise soon.
00:09:31.600 There are scenarios where certain practices lead to confusion.
00:09:35.880 For instance, if you have views overloaded with query logic or repetitive syntax.
00:09:40.020 In such cases, refactor your views by transferring complex code to view helpers.
00:09:44.420 This will yield cleaner views and simplify testing.
00:09:48.000 To summarize, tools like RoboCop or similar can greatly assist you.
00:09:52.020 By automating your process, your code quality will improve.
00:09:55.080 You'll also gain insights into areas that need improvement.
00:09:58.020 Separating refactoring from feature implementations leads to clearer pull requests.
00:10:01.560 Organizing your classes properly, utilizing superclass extractions, and restructuring methods can enhance clarity.
00:10:05.840 Using tools like RoboCop will highlight circular dependencies.
00:10:10.020 Pay attention to the size of controller and model class definitions and conduct thoughtful validation.
00:10:14.160 Always use view helpers judiciously.
00:10:16.560 Keep your views focused; avoid embedding complex queries.
00:10:20.220 Cleaner code results in fewer bugs and more free time to enjoy your vacation.
00:10:25.060 Thank you for your attention, and if you would like my contact information, it's available in the QR code.