- Writing clean code is like riding a bike, you can study physics of it, but still fall down the first time you try to do it
- Programming languages will be created with higher abstractions, and more specific languages related to domains we work will be created(a+), but that doesn’t mean programming will be a lost art. We can’t create machines that do what we want. We can only create machines that can do what we say.
- Leblanc’s law: later equals never
- Companies go bankrupt because of bad code
- Everyone has slowed down because of someone’s bad code. With Greenfield projects you may be adding new features quickly, but with time, cruft is added and you slow down, each feature brings cruft to handle with
- Adding more staff to the project won’t clean cruft. Re-design won’t help because you will race with the old system and it’s features
- Programmers are like doctors, bosses are like patients, doctor won’t listen to his patient and make a mess. You were hired because you know your stuff.
- We won’t know the perfect design for a problem before we start coding a sub-par design and fail. So trying to get stuff perfect for the first time is futile.
- Follow the scout’s rule. Try to leave your codebase more beautiful with each feature you add. Get some extra days to clean that cruft off.
- When people touch bad code to add a feature, they make it worse.
- When someone reads your code, best thing that they can say is: “this was what I expected”. Clean code should be predictable, structured parallel to what job it does.
- Clean code needs tests, otherwise when you make bad design choices, you won’t have the comfort of refactoring with assurance that you did not break anything.
- Duplicate code means there is something In our mind that is not well represented in our code.
- Creating interface for something like saving/getting user from a database, will let you have the option of creating something simpler for the time being, and then implement the real thing later.
- Making code easier to read, makes it easier to write. Because we spend most of our time reading the old code.
- Names should be intention revealing, if you need a comment to explain a variable, you are probably doing it wrong.
- If you give not enough information with the variable names, your code will lose it’s context, and when you pull out a part of it, it won’t be understood by itself. Even if you code simple, and formatting/syntax etc. is good
- Should retrain from giving disinformation with names. You may think hp is a good name for hypothenuse but it is not because it has a more general abbreviation. Only call a variable name list if it is actually a list etc!
- Words with similar shapes should represent similar concepts. If you have two variables in two different modules with different jobs, giving them names like XYZControllerForEfficientHandlingOfStrings and XYZControllerForEfficientStorageOfStrings may not be a good idea.
- You should make meaningful distinctions with your variables. If you are changing a name of the variable because it cannot be the same with another variable, you are just satisfying the compiler. There is probably a good reason why they are 2 different variables. Find that reason.
- Names should be pronounceable. Your code will be talked between other people.
- Noise words are meaningless. You can have 2 different variables productInfo and productData, what are the differences? Customer and CustomerObject?
- Use searchable names. Constant variable definitions instead of hardcoded values are searchable. One word variables may not be.
- Avoid encoding and Hungarian notation.
- Avoid naming interfaces IShape, may prefer Shape and ShapeImp etc.
- Professionals choose clear naming over mind mapping. Don’t name your loop variables l instead of i j etc.
- Avoid words like manager, processor, data, info in the name of a class. A class name should not be a verb.
- Function names should include verbs like post, delete, save, get, set etc. Boolean returning functions should start with appropriate prefix. Like is, should etc.
- Pick one word for concept. Confusing to have fetch, retrieve and get as equivalent methods of different classes in your modules.
- Can use CS terms, algorithm. Names, pattern names, math terms and so forth.
- Use problem domain names.
- ** as your scope gets from small to big, variable names should get longer. Local variables inside methods can have small names, but public variables should have long names. For class methods its the opposite. It is better to have private methods not exposed to outside have longer names, and public methods have shorter names. **
- First rule of functions is that they should be small, second rule is that they should be smaller.
- Extract responsibilities of functions until you can not extract anymore. This usually means functions of 3 to 4 lines of code. Make sure that function only does one thing that can not be extracted any more.
- Don’t worry about function calling overhead, we are in 2019.
- Don’t restate code if you are not changing the level of abstraction.
- Don’t mix levels of abstractions in one function. Such as: #getHTML, Parser#render, String#append etc. This Is confusing
- If you name your functions well, and keep them small, you won’t get lost in pool of functions.
- Step-down Rule: code should be read from top to bottom. Public method should come first, then the private functions that it used should follow. This private functions may have more private functions inside them, they should be hierarchically ordered. However most people put all public methods first then the private ones, so it is better to do it like that, then sort private functions according to call order.
- It is not okay to do the right thing(with naming, function declaration order etc., not architectural decisions etc.) just because you think it is better, while there are established rules in the community/your legacy code base.
- Don’t use switch statements use interfaces and polymorphism. You partition your code into two spaces, app and main. Main is where your code gets wired up, where you have configurations etc, and app is where you have domain specific/implementation specific code. Main can have switch statements.
- Functions should have 3 arguments at most. If you need more arguments, you either need to divide functions into more functions, or you need to group arguments together using a class etc.
- Functions should not accept booleans as arguments, because that means functions accepts that it is behaving in 2 different ways. Create 2 different functions with those behaviors instead. Think if you have 2 booleans as arguments, nightmare.
- Functions should not accept null as arguments. Create 2 functions one that accepts the non-null value of that argument, and one that works without that argument.
- Functions should not use output arguments. Boolean returning arguments should not have side effects, only void returning functions should have side effects.
- Functions should either do something or answer something. Command and Query Separation should be followed.
- Tell don’t ask, you should tell functions what to do, you should not ask them how you should do something. Send commands, instead of querying and calling 2 different functions etc, that should be the responsibility of the object that may “answer” your question. So just put the logic there, don’t ask, just tell!
- Prefer throwing exceptions to returning error codes.
- Try and Catches are ugly on their own right. They obscure the structure of code and mix error processing with normal processing. They should have their own function.
- Error handling is “one thing” you need to separate it out.
- Don’t repeat yourself.
- Every tech team has a coding standard that should be clearly visible by the way you write code. No need to have a separate document for it. If you write a document, that probably means your code is not a good example of it.
- When programmers write comments because they have to, comments become vacuous.
- Comments should be rare if you write good code. They should be only written when programmer attention is needed. Programmers should be grateful comments are there.
- The only reliable documentation of a code is the code itself. Flow charts and high level documentaries should not delve into technical details. They should explain reasons why certain decisions were made etc. The Whys instead of how It was done.
- Comments are failures! However resource constraints may dominate the code structure, or you may need an external documentation with examples etc.
- Comments can not stay up to date. Code will change, comment will be left as it’s old self. Comments are not local, you may change an inner function, and it will invalidate a comment higher up the chain.
- Informative comments that describe regexes, arcane syntax, sql etc. are okay.
- Clarification of Intent. You may leave a comment like todo, warning etc.
- Warning of consequences. You may warn people about the consequences of the code. e.g. a script may break some things, a test may take long etc.
- Public API documentation is OK! But the comments should be readable, don’t put html in the source code.
- Don’t mumble in the comments
- Redundant explanations, if algorithm is clear no need to explain, dry in comments as well. If you explain stuff that are clear in the code, you are doing it wrong.
- Get rid of misleading comments
- Don’t do your VCS’s job, change-log, blame etc. will suffice, don’t comment them.
- Delete commented out codes! VCS!!! —
- File size is not correlated to the project size
- Be disciplined of your uses of blank spaces. Separate variable declarations with other stuff, variables with affinity should be grouped together
- Never scroll to the right. Horizontal bars are demon spawn. 30-40 character long lines are ok. Max should be 100-120
- As long as everybody uses the same indenting style, it is okay.
- Classes that follow tell don’t ask, won’t have many getters, meaning they won’t have observable state. The more you hide internal details, more chance you have using polymorphism. e.g. car class may have getGallonsOfGas or getFuel. Which one makes more sense if you derive car class with ElectricCar?
- Data structures on the other hand have lots of public variables, and no methods. This is the difference between Classes and Data Structures. Methods may be present that expose implementation and are cohesive. And no abstractions are made on those methods.
- Classes manipulate data structures with tell don’t ask. You can for example hold an employee type on an employee data structure, and then use a class that uses switch statements on the data structure’s type field to call appropriate functions.
- Data Structures and Switch statements are related. Classes protect us from new types, but expose us to new methods. Data Structures protect us from new methods but expose us to new types.
- The Expression Problem: Is there a way to get protection from both new types and new methods?
- The key to independent deployability is to know when to use what. We use classes and objects when it is types that are most likely to be added. We use data structures and types when it is methods that are more likely to be added. Think Employee data structure with type enum of Salary and Contract. Or Employee base class and SalaryEmployee and ContractEmployee. —
- Concrete stuff should depend on abstract stuff. E.g. main should depend on app. Database code should depend on domain objects.
- App may use database interface layer, but it should not have knowledge of schema etc.
- The impedance mismatch: between oop and relational databases. Your database do not contain your domain objects. They don’t contain business objects, they don’t contain any object. Databases contain data structures.
- What about hibernate? They are not database to object mappers, they are database to data structures mappers.
- We create classes with hidden data and exposed methods, this makes application easier to understand. Rather than manipulating table rows, we manipulate business objects.
- On application side, we have interfaces that lets us access data we need. On DB interface layer we have classes that implements these interfaces, and map database rows into data structures. Even Hibernate may be used here.
- Code can not be refactored unless you have safety, test suites give you the safety to refactor your rotten code. And remember, every code rots!
- It should be thought as a discipline of doctors. Washing your hands between patients. It is not optional.
- Three laws of TDD: 1-) you are not allowed write production code before you write production code. 2-) you are not allowed to write more of an unit test than is sufficient to fail(even if not compiles) 3-) you are not allowed to write more production code than sufficient enough to pass the current failing unit test
- These laws will interrupts you every 20 seconds, you will switch between writing tests and production code. Write a test enough to fail, write the code for it, repeat.
- High debugging skills are correlated with low productivity. You need to spend more time writing code that works.
- Bugs you create will be the bugs that you have created in the last few minutes.
- Tests create documentations, code examples that everyone can see. They can not get out of sync. It is in a language you understand, and is low level.
- When you write tests, they need to be accessible from the tests, so you decouple.
- You get courage to change! Books are written about design and architecture because we want systems to be flexible, maintainable, and scaleable. We want to change stuff without breaking everything. Good design makes systems flexible, but nothing makes it more flexible than a good suite of tests.
- Follow the tree rules and you will have a good parachute when you jump out of a plane.
- Writing tests after you write your code is boring, because you already know tests pass, you tested them manually. It just becomes a milestone to write tests now.
- Tell your boss that you will write tests, if he objects, tell him that you will double all your estimates, if he objects, tell him to mind his own business. You are a professional.
- A single change in production code may break hundreds of tests, if you design them wrong. Design your tests well. If they become a mess, refactor them. Tests are important as much as your production code.
- Disciplines are pre-made decisions, they are made because we don’t have to think each time, so we won’t think over the same things again and again. Test driven development is a dogma. Thats both a power and a minus. Step back sometimes and question your dogmas and disciplines(e.g. getir micro service template can be good to get work done once you are used to it, but step back and search/experiment for other ways of doing the same thing some times)
- Legacy code is defined as code without tests. If you have a codebase without tests, make series of small decoupling changes. Find a part of the code where you can make decoupling changes, and test that logic. However it is better to wait for new features to be added. Some features can be added without much decoupling refactoring and they will have tests.
- Volatile parts of the code will be touched a lot with each feature/bug, you can start by refactoring them.
- For GUI testing, you may not tested how a buton was rendered, but you can test the decision to make that button disabled or enabled. We do this by MVP architecture. We test the presenter.
- Test driven development is double entry book keeping.
- QA should find nothing. It is professional to ship code that you know will work!
- Aim should be 100%, you should push for it, doesn’t mean that it is not attainable will make us stop trying.
- You don’t need to convince rest of the team. This is a personal decision. You are making the commitment. Let other people take of themselves.
- Architecture is not about tools and materials. You can’t say the architecture is MVC, java, spring and eclipse. That is like saying a house is built with nail, hammer, wood. It is about USAGE.
- A good architecture will scream at you. Telling you its use cases. If you look at a program and all you see is model view controller(separating folders by layers, but what about micro services? Lambdas?), then the architecture is hiding the use cases, and exposing the delivery system.
- Use cases should stand alone. UI should be separated from business layer decisions. Defer them. Keep options as open as possible when coding. A good architecture maximizes the decisions, that are not made(interfaces?).
- Fitnesse example. Bob and Mike first started implementing WikiPage code with a mock implementation of persistence, then came hash map implementation of it, then they created a file system double that saved wikis to disk. In the end, they noticed they did not need mysql at all. Deferring until the end was okay. Then a company came, wanting the mysql implementation of the same thing, because of a policy. They showed him the test implementations, mysql implementation was done by the next day :D
- Good architectures allow you to defer the choices about ui, database, they maximize your choices! Even like the dependency injection framework :D
- Separating UI, Database from the use cases and deferring the choices lets you give more intelligent choices. When you are making decisions, you can compare between your choices according to the use-cases already implemented. Cost vs Value analysis can be done.
- When we say an accounting system that is delivered over the web. The architecture of the system should scream Accounting, not web. Web is the detail. It should be deliverable by web and console if necessary.
- Making delivery independent architecture is to write use case codes without delivery specific keywords and concepts. Like links, buttons etc.
- Use case is a formal definition of how a user interacts with a system in order to achieve a specific goal.
- A use case is created without delivery specific details, that explains how a specific action is to be done. Like adding an item to a cart, and making an order. This list of specific actions is considered to be primary course. The failures and handling of them are extensional/exception courses. See Alistair Cockburn’s Writing Effective Use Cases book. See User Stories Applied by Mike Cohn.
- Entities, Boundaries, Interactors.
- Entities have application independent business rules. For example Order entity can be used by order fulfillment, catalog and inventory system. It should not contain any method specific to one of those applications.
- Interactors: any application specific code belongs into interactor objects. Think of use cases such as CreateOrder, OrderItem these belong to OrderEntry system, therefore these are specific to OrderEntry system.
- CreateOrderInteractor may call getId and create method of order entity. (Think services in getir)
- Boundary objects isolate interactors from the implementation details. They get user input data, call interactions, prepare the output and give it to the user. It can be HTTP, Lambda, Queue etc. etc. (Think controllers and routes in getir)
- Application specific behavior goes to interactors, application agnostic behavior is entity objects controlled by interactors, Boundary objects communicated with interactions.
- MVC objects are not business objects. MVC should be different from the business objects. Because they are usually aggregation of multiple business objects, or results of use case interactors. You won’t want to use those as business objects.
- Routing is done, a controller is called, controller extracts data from the request and creates a simple request model. This model is just a data structure that the interactor accepts. It does it is job and returns a response model. This response model is handled by boundary and returned in a way that is suitable for http.
- What about database? Does your Entity objects derive from ActiveRecord in Rails, or are they Hibernate records? Probably not. You need a way to map your database records to entity objects. Remember the impedence mismatch. You need a layer to convert database records into entity objects. Interactors know when to open, commit, rollback a transaction. They know how to manipulate the database through the layer. They use the database abstractions on the application side.
- Early analysis of the use cases will led to better business entity modeling.
- Architects that make high level decisions and never code quickly become irrelevant. They don’t sleep in the beds they make for the programmers :D Write code with them, pair program with them. See the real architectural problems.
- Pattern is a named solution to a problem in a context. For example BisectedOvalPattern can be used for technique of drawing faces from up-front as lines.
- Patterns gives us to opportunity to talk about design choices, with our peers.
- Raw experience can teach you enough to play a fair game. But to master the game, you need to study other masters, previous masters, study their approaches, learn what they learn. Look at the patterns of play! Design Patterns!
- Creational patterns help you create instances of objects. Structural patterns helps you set the communication pathways between separate group of objects. Behavioral patterns help you to, partition the behavior of your system into discrete classes.
- Command Pattern: decouples what is done from who does it. For example you may have a sensor that calls actuators, actuator actions can be commands, and sensor can just call an execute method on a command interface.