Hacker News new | past | comments | ask | show | jobs | submit login
Object-Oriented Design Heuristics (2017) [slides] (slideshare.net)
40 points by tjalfi 42 days ago | hide | past | favorite | 17 comments



The book the slides are based on is by far the best book on OOD/P I've ever read. Learned a lot from it. OO is such a hard subject that most of the people end up writing procedural code with classes (looking at you java).


The book you're talking about is it the one mentioned (i quickly looked at the slides): Object Oriented Design Heuristics?


Yes, Object-oriented design heuristics by Arthur J. Riel

https://www.goodreads.com/book/show/179206.Object_Oriented_D...


I try to follow a few rules when writing OO code.

1. Make classes do one thing that is basically summarized in the class name. (This also takes care of 'separation of concerns')

2. Be careful to not over-abstract. That results in many tiny classes, aka ravioli code. Nobody, not even you, will remember what all those little bastards do. 3. If a class is bigger than a page (or two) of code, it's probably too long.

4. Design patterns are for inspiration, not the law. Solve problems, don't prove how smart you are.

5. Make your classes as simple as possible. You will thank yourself later.

6. If you have to write a comment that explains how some confusing thing works; that code block is a good refactoring candidate.

7. Refactor, refactor, refactor. Refactoring is rewriting code for clarity. The more you do this, the more your code will become clear to the reader. I find refactoring often reduces my LOC drastically.

8. If you just learned a technique/pattern and that technique/pattern is suddenly popping up everywhere in your code; make sure you aren't just using it because it's fun. Say you just learned what a singleton pattern is, now your code is littered with singletons. That's a good sign that you are forcing yourself to use your new found toy and reconsider.

9. Long method names. Don't be afraid to have long method names. Yes, it looks ugly, yes it takes time but you will thank yourself later and often a long method name will compensate for the lack of documentation on that method.

10. Your program should basically be a bag of loosely related, self-contained components. Think of them like parts of an engine, they each do one thing very well and they each have an interface to the rest of the engine. Also, they can individually be swapped out for another component (as long as they bolt up to the engine appropriately).

Understanding OOP is definitely a paradigm shift in thinking and it is also loaded with many a foot gun (especially when you are new).


The hardest part of doing good OOP is that the most popular, nominally-OOP language(s) are very bad. Yes, I'm talking about Java. Modern PHP, too.

If I may piggyback off your comment, I'll also throw my two cents in that mostly apply to Java:

1. A class may only ever mutate itself. Never anyone else. So if your Foo class has a method that accepts a Bar as input, you must NOT change that Bar. You may return a new instance of a Bar with different values.

2. Don't use class inheritance unless you are forced to at gunpoint or by a framework you're forced to use.

3. True OOP classes will probably have a deep nesting of dependencies. I.e, a Foo class has a Bar field, which has a Baz field, etc. However, those dependencies should be VERTICAL. Do NOT keep references to other classes that your class does not "own". This creates cyclical dependencies and tight coupling. Most likely somebody else should own both objects at a higher level.

4. If your class has a method that doesn't depend on the object's current state, make it static. (This, with the other points, also hints that the method should not have side effects. Static methods should be pure functions)

5. Each package probably has one public class that actually does stuff. This is your domain model and represents a "workflow" (e.g., managing a user account: create, change profile picture, delete, etc). Any other public types from the package should only be plain old data used by the methods of your domain model.

All that said, I still don't think that OOP is actually the best way to solve most problems.


quick reaction -- OOP - division of ownership for resources, division of duties by logical function ++ ; sequences of actions, managed state in a state-machine, maybe not so great.. distributed, functional decomposition, use a different language built for that..

I wrote C/C++, no BOOST, in 100K LOC size systems, using OOP with simplified rules similar to this; works well for me


My rule for writing OO code is to not write OO code.

Procedural code is:

- simpler to analyze, audit and (formally verify)

- simpler to multithread

- maps to what a program does: a series of transformations applied to an input

- maps to low-level construct in the CPU, i.e. call/ret

- doesn't force you to use heap memory and suffer cache misses due to rampant pointer indirection.

Note: I intentionally avoid saying functional programming.

There is a balance between having no state (Haskell) and allowing mutation because mutation is useful (C, OCaml).


It is true that some parts of your project can benefit from centralised approach (and that sometimes you really need to count every tick) but this fashion of blaming OOP is really weird and should stop.

It is still the best way to architecture big systems. I'm not talking about classes, I'm talking about decentralised, decoupled, small systems, communicating with each other using messages.


I have a hunch that the current trend of applying the actor model to everything is going to be seen as an anti-pattern in a few years. Basically the same way that microservices seemed like the cool thing a few years ago and people now are realizing there's overhead and different classes of bugs/mistakes that aren't always worth the trade-off.


> this fashion of blaming OOP is really weird and should stop

Right. There are many places where OO fits well.

As you mentioned in the medium/large on the services/systems level. But it is also a good paradigm to model pretty much any real, outside thing. A database, a file-system abstraction (loading configs etc.), an external web-service, a peripheral device (keyboard, mouse), a GUI context (the whole thing, not the parts) and so on.

In these cases we want the properties of OOP: local retention, message passing, abstraction through public interfaces and state-machine-like behavior.

The issue arises when we model data with OO: A Person is not an object in your system. It is an associated data-structure. Treat it as such. A User interacts with an interface (CLI, config files, GUI...) treat those as objects. The data about the User is just data about the User.

I don't have an authoritative opinion to offer here. I'm just a dude who writes programs. This is merely the model that works for me.


> But it is also a good paradigm to model pretty much any real, outside thing

If we get to the crux of the matter, what you like there is the tree structure (which definitely is the end result you want) the problem is if you store your data in a tree structure (whether by OO, file system or json document) you cannot easily express that same information but in a different tree layout

Trees couple relationships and things together in such a way that they are hard to tease apart. If you think about your brain what it does is stores the sum of your knowledge in a graph, then at runtime your brain can construct any number of trees from that graph easily

You can think of yourself as being a member of the tree of life and a member of your workplace hierarchy very easily, your data store should be graph like and your query language should be able to pull trees out of at will, checkout Clojure's Fulcro or any EAVT database if you want to see how that works in practise


> In these cases we want the properties of OOP: local retention, message passing, abstraction through public interfaces and state-machine-like behavior.

That's not OOP but Actors or event loops.



I think it's harder (or at least time consuming) to write code like that. Pretty error prone too, when forced repeat manual memory allocation and freeing, etc.

On the flipside, that kind of code is often much easier to debug. For me, the worst cases debugging wise have all been crazy inheritance messes.


You can write procedural without having to resort to manual memory management. C++ and Nim (for citing languages I know the best) gives you tools, destructors for both and for Nim a tunable/deactivable GC, that you allow you to write proceduraly without half your code being about memory management.


(submitter)

The material in the slides is based on the book Object-Oriented Design Heuristics[0] by Arthur Riel.

[1] has a list of the heuristics. It may be incomplete.

[0] https://www.goodreads.com/book/show/179206.Object_Oriented_D...

[1] http://www.vincehuston.org/ood/oo_design_heuristics.html


After a previous discussion here it seems people confuse classes with modules. At the risk of repeating myself.

Class based inheritance - Bad

Traits, roles, mixins - You are still thinking in terms of inheritance so its Meh.

Combining Data and Code - Slow.

Closures, Functors - Cool ... classes with single methods are as "single responsibility" you can get.

Interface Inheritance - Go / protocols from clojure - Better

Interfaces without inheritance - Modules - Best ... the way software is meant to be written. This is the reason why APIs, libraries are so reusable. Modules are like contracts for the function call interface. Because of this you also get the ability to load plugins as opposed to the whole dependency injection.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: