├── .gitattributes ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── code-samples.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-language=Dart 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We are grateful for any contributions made by the community. Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open-source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 4 | 5 | ## Step 1: Fork the Project 6 | 7 | You can get your own version of this project by forking it on GitHub. 8 | 9 | ## Step 2: Create your Feature Branch 10 | 11 | To make your own changes, create a new branch off of "main" and give it a descriptive name. 12 | 13 | ```bash 14 | git checkout -b feature/YourFeatureName 15 | ``` 16 | ## Step 3: Commit your Changes 17 | After you have completed your changes, make sure to commit them and provide a descriptive commit message. 18 | 19 | ```bash 20 | git commit -m 'Add some YourFeatureName' 21 | ``` 22 | ## Step 4: Push to the Branch 23 | Push your changes to your new branch on your forked repository. 24 | 25 | ```bash 26 | git push origin feature/YourFeatureName 27 | ``` 28 | 29 | ## Step 5: Open a Pull Request 30 | Now, you can go to the original repository and open a pull request to the main branch. 31 | 32 | Before you submit your pull request, please make sure to review the contribution guidelines and checklist: 33 | 34 | * The changes are documented. 35 | Thank you for your contribution! 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | MIT License 4 | 5 | Copyright (c) 2023 Babalola Ayotomide(Czar) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dart Clean Code Repository 2 | 3 | Welcome to the Dart Clean Code Repository - a treasure trove for developers seeking to improve their coding skills and write more efficient, readable code. 4 | 5 | This repository was born out of an immersive journey through Robert C. Martin's seminal work, 'Clean Code.' It reflects my endeavor to translate the wisdom and guidelines from this transformative book into practical, executable Dart code. 6 | 7 | As a seasoned developer, my interaction with 'Clean Code' has fundamentally altered my perspective towards writing code, initiating a journey of continual learning and improvement. This repository serves as a testament to this journey and aims to guide others on a similar path. 8 | 9 | Herein, you'll find Dart implementations of key concepts, principles, and patterns derived from 'Clean Code.' Whether you're a novice programmer or an experienced developer, these resources can help you elevate your coding skills, drive your understanding of 'clean' coding practices, and ultimately, make you a better Dart programmer. 10 | 11 | Get ready to embark on an enlightening journey, refining your Dart coding practices, and contributing to cleaner, more efficient codebases. Let's dive in and start the journey towards mastering clean code in Dart! 12 | 13 | 14 | ## Table of Contents 15 | 16 | 1. [Crafting Meaningful Names](#crafting-meaningful-names) 17 | 2. [Creating Efficient Functions](#creating-efficient-functions) 18 | 3. [Perfecting Code Formatting](#perfecting-code-formatting) 19 | 4. [Harnessing Objects and Data Structures](#harnessing-objects-and-data-structures) 20 | 5. [Error Handling Mastery](#error-handling-mastery) 21 | 6. [Understanding System Boundaries](#understanding-system-boundaries) 22 | 7. [Demystifying Unit Tests](#demystifying-unit-tests) 23 | 8. [Designing Effective Classes](#designing-effective-classes) 24 | 9. [Building Robust Systems](#building-robust-systems) 25 | 10. [Unveiling the Power of Emergence](#unveiling-the-power-of-emergence) 26 | 11. [Concurrency in Action](#concurrency-in-action) 27 | 12. [Mastering Successive Refinement](#mastering-successive-refinement) 28 | 13. [Detecting Code Smells and Heuristics](#detecting-code-smells-and-heuristics) 29 | 14. [State Management in Dart](#state-management-in-dart) 30 | 15. [Miscellaneous](#miscellaneous) 31 | 32 | Let's dive in and start the journey towards mastering clean code in Dart! 33 | 34 | ## Crafting Meaningful Names 35 | 36 | The cornerstone of any great piece of code is the use of meaningful and descriptive names. These form the basis of understanding, the blueprint that allows developers to traverse the complex labyrinths of logic and functions without getting lost. 37 | 38 | When crafting names for variables, functions, classes, and other programming constructs in Dart, it is essential to ensure they clearly express their intent. The name itself should tell us why it exists, what it does, and how it is used. 39 | 40 | In this section, we delve into some critical guidelines to follow when naming entities in your Dart code: 41 | 42 | 1. **Use Intention-Revealing Names:** Choose names that specify what a variable holds, a function does, or a class represents. The name itself should be a roadmap to understanding its purpose. 43 | 44 | ```dart 45 | // Bad 46 | int? d; // elapsed time in days 47 | 48 | // Good 49 | int? elapsedTimeInDays; 50 | 2. **Avoid Disinformation:** Stay clear of names that can be misleading or are ambiguous in the context of your code. 51 | ```dart 52 | // Bad 53 | int? lst; // Not clear what 'lst' stands for 54 | 55 | // Good 56 | int? listTotal; 57 | ``` 58 | 3. **Make Meaningful Distinctions:** Don't resort to number series (a1, a2, ...) or noise words (theProduct, myProduct, ...) for distinctions. Every name should have a purposeful and unique distinction. 59 | ```dart 60 | // Bad 61 | var product1 = Product(); 62 | var product2 = Product(); 63 | 64 | // Good 65 | var smartphoneProduct = Product(); 66 | var tabletProduct = Product(); 67 | ``` 68 | 4. **Use Pronounceable and Searchable Names:** If you can't pronounce it, you probably can't remember it. And if you can't search for it, you're in for a frustrating time. 69 | ```dart 70 | // Bad 71 | DateTime? genymdhms; 72 | 73 | // Good 74 | DateTime? generationTimestamp; 75 | ``` 76 | 77 | These structures should give a clear understanding of the importance of meaningful names in code. The examples should illustrate the points made, showing how these principles can be applied in real Dart code. 78 | 79 | ## Creating Efficient Functions 80 | 81 | Functions are the workhorses of any program. They provide the means to encapsulate actions, computations, or operations and use them again and again. They help us organize our code, make it more readable, and easier to maintain. In Dart, we need to ensure our functions are efficient, clear, and easy to use. 82 | 83 | Here are some guidelines for creating efficient functions: 84 | 85 | 1. **Small Functions, Single Responsibility:** Each function should do one thing and do it well. A function should be small, typically no more than a screen of code. 86 | 87 | ```dart 88 | // Bad example 89 | void processOrder(Order order) { 90 | validateOrder(order); 91 | calculateOrderTotal(order); 92 | applyDiscounts(order); 93 | arrangeShipping(order); 94 | } 95 | 96 | // Good example 97 | void processOrder(Order order) { 98 | validateOrder(order); 99 | } 100 | 101 | void calculateOrder(Order order) { 102 | calculateOrderTotal(order); 103 | } 104 | 105 | void applyOrderDiscounts(Order order) { 106 | applyDiscounts(order); 107 | } 108 | 109 | void arrangeOrderShipping(Order order) { 110 | arrangeShipping(order); 111 | } 112 | 2. **Descriptive Names:** Function names should be clear about what the function does. A descriptive name makes it easier to understand what the function does without having to dive into the implementation details. 113 | ```dart 114 | // Bad example 115 | void p(Order order); 116 | 117 | // Good example 118 | void printInvoiceForOrder(Order order); 119 | ``` 120 | 3. **Function Arguments:** The ideal number of arguments for a function is zero. Next comes one, followed by two. Three arguments should be avoided where possible. 121 | ```dart 122 | // Bad example 123 | void createOrder(String id, String customerId, String itemId, int quantity, String shippingAddress); 124 | 125 | // Good example 126 | void createOrder(OrderCreationParameters parameters); 127 | ``` 128 | These are general good practices that can help make your code more manageable and understandable. 129 | 130 | ## Perfecting Code Formatting 131 | 132 | Proper code formatting is an often overlooked but essential aspect of software development. It's about making your code easier to read and understand for yourself, your teammates, and any future developers who might interact with your code. Good formatting practices in Dart consist of several elements: 133 | 134 | 1. **Indentation and Whitespace:** Indentation is used to denote blocks of code. Consistent indentation and the use of whitespace enhance the readability of your code. Dart uses a two-space indent. 135 | 136 | ```dart 137 | // Good example 138 | if (isRaining) { 139 | bringUmbrella(); 140 | } else { 141 | wearSunglasses(); 142 | } 143 | ``` 144 | 145 | 2. **Line Length and Wrapping:** For Dart, the style guide suggests limiting your line length to 80 characters. Lines longer than that should be split into multiple lines. 146 | 147 | ```dart 148 | // Bad example 149 | String report = generateReport(reportName, reportData, true, DateTime.now(), "pdf", true, true); 150 | 151 | // Good example 152 | String report = generateReport( 153 | reportName, 154 | reportData, 155 | isFinal: true, 156 | date: DateTime.now(), 157 | format: "pdf", 158 | includeSummary: true, 159 | includeDetails: true, 160 | ); 161 | ``` 162 | 163 | 3. **Brace Style:** Dart adopts the "K&R style" for braces. The opening brace goes on the same line as the start of the statement and the closing brace lines up with the start of the statement. 164 | 165 | ```dart 166 | // Good example 167 | for (var i = 0; i < 10; i++) { 168 | print(i); 169 | } 170 | ``` 171 | 172 | 4. **Comments:** Comments should be used sparingly and be up-to-date, clear, and concise. It's best to write code that explains itself. 173 | 174 | ```dart 175 | // Bad example 176 | // Subtracting ten 177 | var result = number - 10; 178 | 179 | // Good example 180 | var discountPrice = price - discount; 181 | ``` 182 | 183 | Remember, your code is more often read by humans (including your future self) than by machines. Maintain good formatting habits to keep your code understandable and manageable. 184 | 185 | ## Harnessing Objects and Data Structures 186 | 187 | Effectively managing and manipulating objects and data structures is a key aspect of programming. In Dart, there are many tools at your disposal to efficiently use objects and data structures. Let's dive into a few: 188 | 189 | 1. **Use Objects Appropriately:** In Dart, every variable is an object, and every object is an instance of a class. The object-oriented nature of Dart allows you to encapsulate related data and functions into a single entity. 190 | 191 | ```dart 192 | // A simple Dart class 193 | class Vehicle { 194 | String model; 195 | int year; 196 | 197 | Vehicle(this.model, this.year); 198 | 199 | void printDetails() { 200 | print('Model: $model, Year: $year'); 201 | } 202 | } 203 | 204 | var car = Vehicle('Toyota Corolla', 2020); 205 | car.printDetails(); // Prints: Model: Toyota Corolla, Year: 2020 206 | ``` 207 | 208 | 2. **Choosing the Right Data Structure:** Dart offers a range of data structures like Lists, Sets, and Maps. Understanding their properties and use-cases is essential to utilize them effectively. 209 | 210 | ```dart 211 | // Use a list when order matters 212 | List fruits = ['apple', 'banana', 'cherry']; 213 | 214 | // Use a set when uniqueness is important 215 | Set uniqueFruits = {'apple', 'banana', 'cherry', 'apple'}; // contains {'apple', 'banana', 'cherry'} 216 | 217 | // Use a map for key-value pairs 218 | Map fruitPrices = { 219 | 'apple': 1, 220 | 'banana': 2, 221 | 'cherry': 3, 222 | }; 223 | ``` 224 | 225 | 3. **Using Data Abstraction:** Hide the implementation details and expose only the essential features of an object or a data structure. This can be achieved using encapsulation and getter/setter methods. 226 | 227 | ```dart 228 | class Circle { 229 | double _radius; // private instance variable 230 | 231 | Circle(this._radius); 232 | 233 | // getter method for circumference 234 | double get circumference => 2 * 3.1416 * _radius; 235 | 236 | // setter method for radius 237 | set radius(double radius) => _radius = radius >= 0 ? radius : 0; 238 | } 239 | ``` 240 | 241 | These practices can help improve the readability and maintainability of your code, making it easier to understand and modify as necessary. 242 | 243 | ## Error Handling Mastery 244 | 245 | Handling errors gracefully is critical to ensuring your applications function correctly and can recover from unexpected scenarios. In Dart, this can be accomplished through a variety of mechanisms including try/catch blocks, assertions, and error types. 246 | 247 | 1. **Try/Catch Blocks:** These allow your code to attempt operations that may fail and handle any exceptions that may be thrown. 248 | 249 | ```dart 250 | try { 251 | var result = someFunctionThatMightThrow(); 252 | } catch (e) { 253 | print('Caught an error: $e'); 254 | } 255 | ``` 256 | 257 | 2. **Throwing Errors:** Dart allows you to throw custom exceptions, providing more control over error handling and making your code more descriptive. 258 | 259 | ```dart 260 | if (input < 0) { 261 | throw ArgumentError('Input cannot be negative'); 262 | } 263 | ``` 264 | 265 | 3. **Custom Error Types:** Dart also allows you to define custom exception types to handle specific errors unique to your application's logic. 266 | 267 | ```dart 268 | class NegativeInputError extends Error { 269 | String toString() => 'Input cannot be negative'; 270 | } 271 | 272 | if (input < 0) { 273 | throw NegativeInputError(); 274 | } 275 | ``` 276 | 277 | 4. **Finally Block:** This block is executed regardless of whether an exception was thrown, and is typically used for cleanup code. 278 | 279 | ```dart 280 | try { 281 | var result = someFunctionThatMightThrow(); 282 | } catch (e) { 283 | print('Caught an error: $e'); 284 | } finally { 285 | print('Cleaning up...'); 286 | } 287 | ``` 288 | 289 | 5. **Assertions:** They are useful during development for catching errors and exceptions as early as possible. An assertion disrupts normal execution if a boolean condition is false. 290 | 291 | ```dart 292 | int performCalculation(int input) { 293 | assert(input >= 0, 'Input cannot be negative'); 294 | // Rest of the function... 295 | } 296 | ``` 297 | 298 | Mastering error handling in Dart can help ensure your applications are reliable, resilient, and easier to debug. 299 | 300 | ## Understanding System Boundaries 301 | 302 | Understanding and respecting system boundaries is fundamental to designing robust, maintainable software. These boundaries can exist between classes, modules, libraries, and even between different parts of the same function. In Dart, we can use a variety of techniques to define and work with these boundaries. 303 | 304 | 1. **Decoupling with Interfaces:** Interfaces in Dart can be implicitly implemented, allowing for loose coupling between different parts of a system. This way, we can change the internal workings of a class without affecting its consumers. 305 | 306 | ```dart 307 | abstract class DataProvider { 308 | Future> fetchData(); 309 | } 310 | 311 | class NetworkDataProvider implements DataProvider { 312 | @override 313 | Future> fetchData() { 314 | // Fetch data over the network... 315 | } 316 | } 317 | 318 | class DiskDataProvider implements DataProvider { 319 | @override 320 | Future> fetchData() { 321 | // Fetch data from disk... 322 | } 323 | } 324 | ``` 325 | 326 | 2. **Using Libraries to Encapsulate Code:** Dart allows you to create libraries to encapsulate related code. This can provide clear boundaries and make your code easier to manage and understand. 327 | 328 | ```dart 329 | // in my_library.dart 330 | library my_library; 331 | 332 | part 'src/my_class.dart'; 333 | part 'src/my_other_class.dart'; 334 | ``` 335 | 336 | 3. **Isolate for Concurrency:** Dart's `Isolate` class can be used to run code on a different CPU core, effectively creating a boundary between it and the rest of your code. 337 | 338 | ```dart 339 | Isolate.spawn(myIsolateFunction, 'Hello from the main isolate'); 340 | ``` 341 | 342 | 4. **Boundaries in State Management:** Understanding how state is shared and accessed across your system is crucial. Various state management techniques can help maintain clear boundaries, reducing complexity and potential bugs. 343 | 344 | ```dart 345 | // using Provider for state management 346 | Provider( 347 | create: (context) => CartModel(), 348 | child: MyApp(), 349 | ); 350 | ``` 351 | 352 | By understanding and properly managing system boundaries, you can create code that's easier to test, maintain, and understand. 353 | 354 | ## Demystifying Unit Tests 355 | 356 | Unit tests are a vital part of developing robust and maintainable software. They help verify your code's correctness, make refactoring safer, and can even guide your design if you follow a test-driven development (TDD) approach. In Dart, we have a robust `test` package that facilitates writing unit tests. 357 | 358 | 1. **Writing Simple Unit Test:** A simple test in Dart can be written using the `test` function. Assertions are made using the `expect` function. 359 | 360 | ```dart 361 | import 'package:test/test.dart'; 362 | 363 | void main() { 364 | test('String.split() splits the string on the delimiter', () { 365 | var string = 'foo,bar,baz'; 366 | expect(string.split(','), equals(['foo', 'bar', 'baz'])); 367 | }); 368 | } 369 | ``` 370 | 371 | 2. **Grouping Tests:** Tests can be grouped together using the `group` function. This makes tests easier to manage and read. 372 | 373 | ```dart 374 | import 'package:test/test.dart'; 375 | 376 | void main() { 377 | group('String.split()', () { 378 | test('splits the string on the delimiter', () { 379 | var string = 'foo,bar,baz'; 380 | expect(string.split(','), equals(['foo', 'bar', 'baz'])); 381 | }); 382 | 383 | test('returns the string if no delimiter is present', () { 384 | var string = 'foobar'; 385 | expect(string.split(','), equals(['foobar'])); 386 | }); 387 | }); 388 | } 389 | ``` 390 | 391 | 3. **Mocking and Stubbing:** For more complex unit tests, you might need to stub or mock certain behaviors. The `mockito` package is a popular choice for this in Dart. 392 | 393 | ```dart 394 | import 'package:mockito/mockito.dart'; 395 | import 'package:test/test.dart'; 396 | 397 | class MockFoo extends Mock implements Foo {} 398 | 399 | void main() { 400 | test('Mocking example', () { 401 | var mockFoo = MockFoo(); 402 | when(mockFoo.someMethod('valid input')).thenReturn('expected output'); 403 | expect(mockFoo.someMethod('valid input'), equals('expected output')); 404 | }); 405 | } 406 | ``` 407 | 408 | Remember, unit tests are your friends. They can save you from future headaches by catching bugs early in the development process. They can also serve as a form of documentation, showcasing how your code is supposed to work. 409 | 410 | ## Designing Effective Classes 411 | 412 | Designing effective classes is a fundamental aspect of object-oriented programming (OOP). In Dart, classes provide a means to encapsulate data and methods that work on that data. A well-designed class can be reused in various contexts, making your code more efficient and maintainable. 413 | 414 | 1. **Creating a Basic Class:** In Dart, a class can be created using the `class` keyword. 415 | 416 | ```dart 417 | class Animal { 418 | String name; 419 | 420 | Animal(this.name); 421 | 422 | void makeSound() { 423 | print('$name makes a sound'); 424 | } 425 | } 426 | ``` 427 | 428 | 2. **Inheritance and Method Overriding:** Dart supports single inheritance. Subclasses can override the methods of the superclass. 429 | 430 | ```dart 431 | class Dog extends Animal { 432 | Dog(String name): super(name); 433 | 434 | @override 435 | void makeSound() { 436 | print('$name barks'); 437 | } 438 | } 439 | ``` 440 | 441 | 3. **Implementing Interfaces:** Dart classes can implement one or more interfaces. This enforces a contract of methods that the class must define. 442 | 443 | ```dart 444 | class Cat extends Animal implements Playful { 445 | Cat(String name): super(name); 446 | 447 | @override 448 | void makeSound() { 449 | print('$name meows'); 450 | } 451 | 452 | @override 453 | void play() { 454 | print('$name plays with a ball of yarn'); 455 | } 456 | } 457 | ``` 458 | 459 | 4. **Creating Private Members:** In Dart, class members (both fields and methods) can be made private by prefixing their name with an underscore. 460 | 461 | ```dart 462 | class Secretive { 463 | String _privateField; 464 | 465 | void _privateMethod() { 466 | // ... 467 | } 468 | } 469 | ``` 470 | 471 | Designing effective classes involves more than just understanding these basic concepts. It requires careful thought and planning to create classes that are easy to understand, flexible, and efficient. Remember the principles of SOLID design and strive to create classes that have a single responsibility, are open for extension but closed for modification, and have clearly defined interfaces. 472 | 473 | ## Building Robust Systems 474 | 475 | Building robust systems is a core responsibility of every developer. Robust systems are characterized by their ability to handle unexpected conditions gracefully, maintain performance under stress, and recover quickly from failures. In Dart, there are several practices that can contribute to system robustness. 476 | 477 | 1. **Effective Error Handling:** We've already discussed error handling in depth. Consistent, effective error handling is a cornerstone of a robust system. 478 | 479 | ```dart 480 | try { 481 | performRiskyOperation(); 482 | } catch (e) { 483 | // Log and handle error 484 | } 485 | ``` 486 | 487 | 2. **Concurrency and Asynchronicity:** Dart's `Future` and `async/await` syntax allow for easy management of asynchronous operations. Asynchronous programming can greatly improve system performance and responsiveness. 488 | 489 | ```dart 490 | Future loadData() async { 491 | // Async operation 492 | } 493 | ``` 494 | 495 | 3. **State Management:** Managing the state of your application effectively can reduce bugs and improve performance. Various approaches can be adopted, such as the BLoC pattern. 496 | 497 | ```dart 498 | class CounterBloc extends Bloc { 499 | @override 500 | int get initialState => 0; 501 | 502 | @override 503 | Stream mapEventToState(CounterEvent event) async* { 504 | // Map CounterEvent to appropriate state 505 | } 506 | } 507 | ``` 508 | 509 | 4. **Unit and Integration Testing:** Tests ensure that your system behaves as expected and help catch regressions. Dart provides a strong testing library for both unit and integration tests. 510 | 511 | ```dart 512 | void main() { 513 | test('description', () { 514 | // Test case 515 | }); 516 | } 517 | ``` 518 | 519 | 5. **Code Reviews and Pair Programming:** These practices provide another set of eyes to catch issues and provide feedback, making your code more robust and maintainable. 520 | 521 | Remember that building robust systems is not just about writing good code, but also about following best practices, continuously learning and refining your approach, and understanding and fulfilling the requirements of your users. 522 | 523 | ## Unveiling the Power of Emergence 524 | 525 | Emergence in the context of software engineering refers to the phenomenon where simple rules or behaviors at the lower level can lead to complex and sophisticated behaviors at the higher level. When we structure our code well, maintain good practices and follow established principles, we enable emergence - our codebase starts to exhibit behaviors that are greater than the sum of its parts. 526 | 527 | 1. **Run All Tests:** Ensuring that all unit tests are passing at all times is a simple practice that greatly contributes to emergence. It ensures that new changes don't break existing functionality. 528 | 529 | ```dart 530 | // Example of a basic test in Dart 531 | import 'package:test/test.dart'; 532 | 533 | void main() { 534 | test('my first unit test', () { 535 | var answer = 42; 536 | expect(answer, 42); 537 | }); 538 | } 539 | ``` 540 | 541 | 2. **Refactor Code:** Continually refactoring and improving the structure of your code leads to a more manageable and understandable codebase. 542 | 543 | ```dart 544 | // Before refactoring 545 | var a = 3; 546 | var b = 4; 547 | var c = a * a + b * b; 548 | print(c); // prints 25 549 | 550 | // After refactoring 551 | int calculateHypotenuse(int a, int b) { 552 | return a * a + b * b; 553 | } 554 | print(calculateHypotenuse(3, 4)); // prints 25 555 | ``` 556 | 557 | 3. **Expressive and Consistent Naming:** Using expressive and consistent names for variables, functions, classes, etc. can significantly improve the readability of your code, facilitating better understanding and cooperation among team members. 558 | 559 | ```dart 560 | // Before: Not very expressive 561 | var p = getPerimeter(3, 4); 562 | 563 | // After: Much more expressive 564 | var rectanglePerimeter = calculateRectanglePerimeter(3, 4); 565 | ``` 566 | 567 | The power of emergence isn't about doing one big thing right, but doing many small things right. When you start applying these principles, over time, you will see your codebase becoming more maintainable, scalable, and robust. 568 | 569 | ## Concurrency in Action 570 | 571 | Concurrency in programming refers to the ability of a system to handle multiple tasks at once. In a concurrent system, several computations are executing simultaneously and potentially interacting with each other. Understanding and managing concurrency in Dart can greatly improve the responsiveness and performance of your applications. 572 | 573 | 1. **Understanding `Future` and `async/await`:** Dart makes handling concurrency easier with the use of `Future` objects and `async/await` syntax. 574 | 575 | ```dart 576 | // A function that returns a Future that produces a string 577 | Future fetchUserOrder() { 578 | return Future.delayed(Duration(seconds: 2), () => 'Cappuccino'); 579 | } 580 | 581 | // Using async/await to wait for the Future to complete 582 | void main() async { 583 | print('Fetching user order...'); 584 | var order = await fetchUserOrder(); 585 | print('Your order is: $order'); 586 | } 587 | ``` 588 | 589 | 2. **Leveraging `Stream`:** A `Stream` in Dart is a sequence of asynchronous events. It's a way of getting data piece by piece, instead of getting it all at once. 590 | 591 | ```dart 592 | // A function that produces a Stream 593 | Stream countStream(int to) async* { 594 | for (int i = 1; i <= to; i++) { 595 | await Future.delayed(Duration(seconds: 1)); 596 | yield i; 597 | } 598 | } 599 | 600 | // Consuming a Stream using an async for-loop 601 | void main() async { 602 | await for (var count in countStream(5)) { 603 | print('Count: $count'); 604 | } 605 | } 606 | ``` 607 | 608 | Concurrency is a challenging concept, but with the right tools and understanding, you can write code that handles multiple tasks simultaneously, making your applications more efficient and responsive. 609 | 610 | ## Mastering Successive Refinement 611 | 612 | Successive Refinement is the practice of continuously refining and improving your code over time. It's the process of revisiting code written earlier, understanding it, and making improvements while maintaining functionality. 613 | 614 | 1. **Continual Improvement:** Write the first draft of your code to work, then refine it to make it cleaner and efficient. Code refinement should be a continuous process. 615 | 616 | ```dart 617 | // First Draft 618 | bool isPrime(int n) { 619 | if (n <= 1) { 620 | return false; 621 | } 622 | for (int i = 2; i < n; i++) { 623 | if (n % i == 0) { 624 | return false; 625 | } 626 | } 627 | return true; 628 | } 629 | 630 | // Improved Version 631 | bool isPrime(int n) { 632 | if (n <= 1) return false; 633 | if (n == 2) return true; 634 | if (n % 2 == 0) return false; 635 | for (int i = 3; i * i <= n; i += 2) { 636 | if (n % i == 0) return false; 637 | } 638 | return true; 639 | } 640 | ``` 641 | 642 | 2. **Keep the Code DRY:** 'DRY' stands for 'Don't Repeat Yourself.' If you find yourself writing the same code more than twice, consider creating a function or class to encapsulate that functionality. 643 | 644 | ```dart 645 | // Repeated Code 646 | print('Fetching data...'); 647 | var data = await fetchData(); 648 | print('Data fetched.'); 649 | 650 | print('Fetching more data...'); 651 | var moreData = await fetchMoreData(); 652 | print('More data fetched.'); 653 | 654 | // DRY Code 655 | Future fetchDataWithLog(Future Function() fetchFunction) async { 656 | print('Fetching data...'); 657 | var data = await fetchFunction(); 658 | print('Data fetched.'); 659 | return data; 660 | } 661 | 662 | void main() async { 663 | var data = await fetchDataWithLog(fetchData); 664 | var moreData = await fetchDataWithLog(fetchMoreData); 665 | } 666 | ``` 667 | 668 | Remember, the goal of successive refinement isn't to write perfect code from the get-go. Instead, it's about recognizing that your understanding and the quality of your code will improve over time. 669 | 670 | ## Detecting Code Smells and Heuristics 671 | 672 | Code smells are indicators in the code that suggest a problem that needs attention. These are not bugs, but rather symptoms of poor design or implementation choices. Heuristics, on the other hand, are experienced-based techniques for problem-solving, learning, and discovery. 673 | 674 | Here are a few examples: 675 | 676 | 1. **Duplicate Code:** If the same code structure is found in more than one place, it might be a good idea to encapsulate it in a single function or class. 677 | 678 | ```dart 679 | // Duplicate Code 680 | void printStudentDetails(Student s) { 681 | print('Name: ${s.name}'); 682 | print('Age: ${s.age}'); 683 | print('Grade: ${s.grade}'); 684 | } 685 | 686 | void printTeacherDetails(Teacher t) { 687 | print('Name: ${t.name}'); 688 | print('Age: ${t.age}'); 689 | print('Subject: ${t.subject}'); 690 | } 691 | 692 | // Refactored Code 693 | void printPersonDetails(Person p, String additionalInfo) { 694 | print('Name: ${p.name}'); 695 | print('Age: ${p.age}'); 696 | print(additionalInfo); 697 | } 698 | ``` 699 | 700 | 2. **Long Function or Class:** A function or class that has grown too large is hard to understand and maintain. Consider breaking it down into smaller, more manageable pieces. 701 | 702 | ```dart 703 | // Long Function 704 | void doEverything() { 705 | // hundreds of lines of code... 706 | } 707 | 708 | // Refactored Code 709 | void doPart1() { /* ... */ } 710 | void doPart2() { /* ... */ } 711 | void doPart3() { /* ... */ } 712 | 713 | void doEverything() { 714 | doPart1(); 715 | doPart2(); 716 | doPart3(); 717 | } 718 | ``` 719 | 720 | 3. **Comments explaining complex code:** If you need a comment to explain what a bit of code does, it might be a sign that the code is too complex. Consider refactoring it to make its purpose clearer. 721 | 722 | ```dart 723 | // Complex Code 724 | void calculate() { 725 | // Complex calculations... 726 | } 727 | 728 | // Refactored Code 729 | void calculate() { 730 | prepareData(); 731 | performCalculations(); 732 | storeResults(); 733 | } 734 | ``` 735 | 736 | Detecting these code smells and others can help you keep your code clean and maintainable. Remember, these are heuristics, not hard and fast rules. Use your judgment and experience to determine when and how to apply them. 737 | 738 | ## State Management in Dart 739 | 740 | State Management is a crucial aspect of any application. In Dart, and specifically Flutter, there are various ways to handle state management. It determines how we store and share data across our application, affecting the app's performance and usability. 741 | 742 | Here's a quick overview of some common state management techniques in Dart: 743 | 744 | 1. **Provider:** Provider is a dependency injection system built with widgets for widgets. It mixes dependency injection (DI) and state management to ensure objects consume dependencies without requiring the manual passing of a reference from one widget to another. 745 | 746 | ```dart 747 | // Provider example 748 | void main() { 749 | runApp( 750 | ChangeNotifierProvider( 751 | create: (context) => CounterModel(), 752 | child: MyApp(), 753 | ), 754 | ); 755 | } 756 | 757 | class CounterModel with ChangeNotifier { 758 | int _count = 0; 759 | int get count => _count; 760 | 761 | void increment() { 762 | _count++; 763 | notifyListeners(); 764 | } 765 | } 766 | ``` 767 | 768 | 2. **Riverpod:** Riverpod is a robust way to handle state management. It overcomes some of the limitations of Provider, such as allowing providers to be consumed anywhere without context and offering an improved mechanism to deal with Flutter's widget lifecycle. 769 | 770 | ```dart 771 | // Riverpod example 772 | final counterProvider = StateProvider((ref) => 0); 773 | 774 | class Counter extends ConsumerWidget { 775 | @override 776 | Widget build(BuildContext context, WidgetRef ref) { 777 | final count = ref.watch(counterProvider); 778 | return Text('$count'); 779 | } 780 | } 781 | ``` 782 | 783 | 3. **Redux:** Redux is a predictable state container that helps to write applications that behave consistently across different environments (client, server, and native). 784 | 785 | ```dart 786 | // Redux example 787 | // Defining the State 788 | class AppState { 789 | final int count; 790 | AppState({this.count = 0}); 791 | } 792 | 793 | // Reducer 794 | AppState reducer(AppState state, dynamic action) { 795 | if (action == Actions.Increment) { 796 | return AppState(count: state.count + 1); 797 | } else { 798 | return state; 799 | } 800 | } 801 | 802 | // Store 803 | final store = Store(reducer, initialState: AppState()); 804 | ``` 805 | 806 | Remember, choosing a state management strategy depends on your project's complexity, size, and your team's familiarity with the pattern. Strive for consistency, predictability, and understandability when making your choice. 807 | ## Miscellaneous 808 | 809 | In this section, we tackle a variety of additional topics that don't neatly fit into our previous categories, yet remain crucial in the pursuit of clean code. This includes areas such as security considerations, optimization techniques, and maintaining code agility as projects scale. 810 | 811 | First up, let's delve into Class Modifiers within the scope of Clean Code principles: 812 | 813 | 814 | ### Exploring Class Modifiers in Clean Code 815 | 816 | In Dart, class modifiers control the usage of a class or mixin within its own library and beyond. The set of modifiers include: 817 | 818 | - abstract 819 | - base 820 | - final 821 | - interface 822 | - sealed 823 | - mixin 824 | 825 | They precede a class or mixin declaration and influence the expected behaviours of the class. 826 | 827 | Consider the following examples illustrating their usage: 828 | 829 | ```dart 830 | // 'abstract' modifier 831 | abstract class AbstractEntity { 832 | void save(); 833 | } 834 | 835 | // 'base' modifier 836 | base class User extends AbstractEntity { 837 | String name; 838 | 839 | @override 840 | void save() { 841 | // Implementation for saving a user 842 | } 843 | } 844 | 845 | // 'final' modifier 846 | final class Admin extends User { 847 | @override 848 | void save() { 849 | // Implementation for saving an admin 850 | } 851 | } 852 | ``` 853 | Class modifiers are invaluable tools for managing class behaviors and safeguarding the integrity of your codebase. A sound understanding and correct application of these modifiers is a key aspect of writing clean, maintainable code. 854 | 855 | For a comprehensive study of these class modifiers, please refer to the Dart documentation [here](https://dart.dev/language/class-modifiers). 856 | 857 | ## Conclusion 858 | 859 | Mastering the art of clean, efficient, and maintainable code is an ongoing journey. We've covered a range of topics in this repository from creating meaningful names and crafting efficient functions, to understanding system boundaries and mastering successive refinement, and even touched upon important topics like state management in Dart. 860 | 861 | We've also explored the fascinating world of class modifiers in Dart, shedding light on `abstract`, `base`, `final`, `interface`, `sealed`, and `mixin`. 862 | 863 | This is just a stepping stone in your path to becoming a better Dart developer. As you continue to grow and learn, remember to keep refining and polishing your skills. Programming is a craft, and like any craftsperson, you should take pride in your work. 864 | 865 | Your code is a reflection of your understanding and your thought process. Make it count. It's not just about making your code work, it's about crafting a piece of art that stands the test of time. Happy coding! 866 | 867 | Remember, there's no endpoint to learning in this field. So, stay curious, keep exploring, and most importantly, enjoy the process! 868 | 869 | # Contributing 870 | 871 | We appreciate all contributions to our project! If you're interested in contributing, please refer to our `CONTRIBUTING.md` file. It provides a detailed guide on how to get started with contributing to our codebase. We look forward to your suggestions, bug reports, and pull requests. 872 | 873 | -------------------------------------------------------------------------------- /code-samples.dart: -------------------------------------------------------------------------------- 1 | // code samples goes in here 2 | --------------------------------------------------------------------------------