One of the best books on programming style and function, backed up with actual research for the recommendations.
Also, The Pragmatic Programmer is good, for topics both about programming and beyond programming per se.
Of books that are more on the craft of programming, "Refactoring" (Also Fowler) is good. And I enjoyed "Practical Common Lisp" by Peter Seibel too.
In general, I suspect that the value of a book has more to do with where the reader is, than where the book is.
(in roughly chronological order. As you can see from the chronology, it took me a long time to start to like OOP.)
Edit: Just realised that it is available under a CC license: http://thinking-forth.sourceforge.net/
That's interesting. Can you elaborate on why you think it dominates your approach? I'm interested because I had read a good amount of Starting Forth by Leo Brodie some years ago, and liked the language, and had played around with it a bit (only got access to a Forth system for a short time, so could not go deeply into it, on the practical side). But I remember liking the language. I don't think I have read Thinking Forth.
Also, though I don't think this was quite in the book, it helped lead me to the idea of refactoring and its synergy with automated testing almost a decade before the idea started spreading.
Later when I learned about refactoring and TDD, it felt really natural. Until now I hadn't made the connection, but it was exactly the same thing I was doing when I was writing FORTH code.
I also program everything bottom up, rather than top down. I'm pretty sure this is from the influence Thinking Forth had on me as a programmer. I'll make a mental sketch of how things will fit together overall, pick a spot, drill down and then start building the pieces I'm going to need. I refactor as I go. Finally I plug everything together. This also leads me to do subsystem decomposition very, very late in the process. Only after I've built the pieces do I start thinking about where they should ultimately end up. This makes refactoring a lot easier: first when you are building the pieces because you don't have to jump through hoops to get at things, and second when you are assembling because the pieces will naturally fall into places based on what data they interact with. If you have problems understanding where a piece should go, then it's a good indication that you have more refactoring to do.
Some people hate the way I do evolutionary design :-) It works for me, though.
Her books and her talks had the biggest influence on me regarding code design. At least influenced to think a lot more about it.
Learning Ruby itself was a huge influence to me; hadn't considered that a language should be designed to make programmers "happy", as Matz said. "Confident Ruby" was one of several books that had this human-happiness focus. "Confident" is broken down into patterns, many of which can be found in books like Sandi Metz's POODR, but as a semi-experienced programmer, Grimm's way of writing really appealed to me.
Even the title of the book itself was revelatory to me. The idea that the functions and methods and conventions we create should be rooted in a "confident" mentality (such as the old adage of being promiscuous in what a function accepts, and strict in what it returns) really improved the way I designed code. Not just in terms of technical proficiency, but with less cognitive burden, which ultimately leads to the elegant simplicity we desire in our work.
http://www.virtuouscode.com/2015/08/11/what-its-like-to-come...
FWIW, I almost never use Ruby today, having switched to Python for both teaching and development purposes. That doesn't mean that learning Ruby didn't influence me. For starters, it taught me the dangers of giving programmers too much syntactic freedom :).
Because it is mine, for better or worse. It took me from understanding OO to understanding how to build large systems with OO, writing maintainable code and using proper encapsulation. A much deeper work than Code Complete (though the latter is worth reading too).
I have used functional languages as well (ML, Lisp; not Haskell yet though I think I get what monads are; plus I write a lot of Python, C++, JS in functional style) but I have yet to use them for any project as big. I hope I get the chance to one day. But for now Design Patterns gets my vote.
Apparently so; sad.
http://www.perlmonks.org/?node_id=133399 does a very good job of explaining why Design Patterns is a book to be careful with. By contrast Code Complete won't steer people wrong.
See https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo... for a cautionary tale about how a pursuit of OO purity can result in great verbosity, hiding intent behind a barrage of patterns. Resulting in code that I, for one, would rather not work with.
But then again I'm hardly enthusiastic about OO. See http://www.perlmonks.org/?node_id=318257 for more on my perspective.
DP are oft misused, but I think the main idea is a pattern language - a way to communicate to other developers what the architecture is. This is especially important on larger, more complex projects with more developers and developer turnover. Understanding the architecture of a project is crucial for grokking it, but usually not well documented. Even just "DPs are naming conventions" is helpful in this regard.
I really disagree with the idea that most DPs are particularly clever or helpful - they are just the way you'd end up solving those kinds of problems yourself. If you look at your own pre-DP code, you'll see design patterns. What they give is a standardized terminology ("pattern language").
Sometimes an architecture seems more complex than necessary, but makes sense as you better understand the whole problem. e.g. how curl handles http. e.g. youtube-dl. (for me, when I reverse-engineered mini-versions of them, then studied their source)
tl;dr "DPs are docs"
1. Being able to use functional programming concepts in OO languages made a lot of design patterns irrelevant (Python, Ruby, C# 3.5, C++11, Java 8). I feel Crockford's The Good Parts really introduced these concepts to the OO crowd with javascript acting as the bridging language for the concepts.
2. Many design patterns were terrible ideas in practice. Factory patterns were a disaster, as were many of the others. Good in theory, utterly useless in practice. There was a blog here recently about TDD that argued TDD is useless because you need to be a great architect to do TDD correctly, and most programmers aren't great architects. Design patterns had the same problem, it was very easy to get them wrong and create an utter mess. Ultimately it's more maintainable to have a complicated monolith method than some crazy pattern no-one understands or dare touch.
Implementation-recipe design patterns only need to exist (or, rather, only need to be part of the active, day-to-day awareness of more than a small number of programmers per language) when the target language does not support implementing the common solutions as reusable code modules because doing so would require a kind of abstraction the language does not support.
This has nothing to do with the OOP paradigm particularly; there's no reason OOP languages can't support powerful abstractions that reduce the need for that type of design patterns; the GoF book just emerged when the most popular industrial languages were both OOP and lacking in those facilities.
- In Java [2] or C++, to implement that pattern, you may have to define an abstract class, or a class with some abstract and some concrete methods, and subclass it, and in the subclass, redefine some of the abstract methods (of the superclass) concretely. (Maybe interfaces can be used here instead of abstract classes/methods - in Java. Haven't used Java for a while, so not sure right now.)
- In Python, you can just do:
def transform(input, output):
# Code here to read data from input, transform it, and
# write the transformed data to output.
Now let's say the code in the transform function does input.read() and output.write() (with maybe some way to indicate when the end of input is reached, like the null string is used in Python). In other words, methods like those of Python file objects are used for reading and writing. Given all this, you can now pass objects of any types as arguments for the input and output parameters, as long as they implement a read and a write method respectively, and the code will just work.This has been described as "smooth seamless polymorphism" in Python. It's also known as duck typing.
https://en.wikipedia.org/wiki/Duck_typing
[1] The Template Method Pattern is one of the ways in which frameworks (as opposed to libraries) are implemented. The abstract methods of the framework are implemented concretely by your code that the framework then calls, using the Hollywood principle - Don't call me, I'll call you, a.k.a. Inversion of Control (IoC).
https://en.wikipedia.org/wiki/Template_method_pattern
https://en.wikipedia.org/wiki/Inversion_of_control
[2] Note that what I wrote is based on knowledge of older versions of Java - newer versions may have features that make the pattern simpler to implement, maybe without abstract classes/methods.
The Command pattern, in its most basic form, could also be considered as implementing 1st class functions by wrapping them into objects.
So if you're using a functional langage, there's a good chance you're wondering why consider both of the above as "design patterns", as you're seing them as basic things, and the compiler implements them for you.
The visitor pattern addresses doing multiple dispatch in a single dispatch language.
> The Command pattern, in its most basic form, could also be considered as implementing 1st class functions by wrapping them into objects.
BTW, that's not really true; first class functions are not a replacement for commands. Commands are about turning behavior into state so it can be stored and executed, potentially replayed for example, at a later time. Commands aren't just functions, even languages that have first class functions still use the OO command pattern when doing things that benefit from command patterns.
First-class functions plus partial application do that trivially. (First-class functions provide the ability to store a generic command, partial application let's you get a specific instance with local state that can be “replayed” against a different external state object.)
Many times commands come with multiple actions, to allow execution and rollback, require being able to be serialized to disk to database and replayed later, or are used to better organize and plug in extension commands to large code bases. None of these cases are amenable to first class functions.
The command pattern isn't just a hack around not having first class functions, we still use command even when first class functions are available. You aren't going to extend Photoshop with a new filter using a first class function; you're going to write a plugin which is a command pattern for filter execution.
Stop trying to tell me what first class functions can do, I'm fully aware, I use them every day and have for nearly 20 years. You aren't talking to some kid who never heard of functional programming.
> BTW, that's not really true; first class functions are not a replacement for commands.
Please note that you're extrapolating from my post: this isn't what I said.
We still need this pattern: there's no way you're going to implement "undo" using only first-class functions, and I'm well aware of that.
In some specific situations, though, we don't need it anymore, like when connecting a GUI "save" button to a "document.save" function.
False. Lisp has macro's and can abstract anything syntactically, yet still has commonly recurring design patterns such as with-X macros. You misunderstand what a design pattern is if you think new language features can remove the need for them.
16 of 23 patterns are either invisible or simpler, due to [...]
Those languages are higher-level languages. A considerable number of design patterns disappears in them already.
In Java or C++, you have functions with parameters and local variables.
In assembly language, there is a "design pattern" of pushing words onto a stack to generate arguments, and moving the stack pointer to reserve a frame for local variables, and then unwinding these actions when the function returns. Also, saving certain registers and restoring them may be part of the pattern. This pattern can be supported with macros and pseudo-ops to varying degrees.
In these higher level language, this pattern disappears. The concept of parameters and local variables doesn't disappear, of course. Those things are just declared in a simple way.
The assembly language pattern for calling conventions is in fact a back-formation from higher level languages. It won't even occur to assembly language programmers until they think of their programs at a higher level, and translate from that to machine code.
Or: let's look at C++ vs C. In C++, there are declarative mechanisms which generate code that can be understood in terms of C. It captures coding patterns, such as ensuring that a structure is properly initialized when instantiated, and cleaned up on every exit path from a function.
When it does have the expressivity, the underlying rendering of the pattern hasn't gone away; just the manual boiler-plate for producing it.
For instance, if you're working in assembly language, then you might sometimes benefit from a "while loop design pattern" to keep your code clean. The while loop design pattern calls for some test code before a block which jumps past the block when the test is negative, and an unconditional backward branch at the end of the block back to that test expression.
The while loop doesn't go away when you use a higher level language. You just indicate that you would like that pattern, and the compiler spits it out for you, invisibly.
> Every time you pass a lambda to a higher order function you're using a strategy pattern.
Every time you pass a "naked" lambda somewhere, you're potentially missing the opportunity to have a macro there do that for you.
True but equally sad that you must use Lisp to hide ugly Lisp because naked lambda's are so ugly. Yet the point remains, whether hidden behind a macro or not, design patterns still exist in Lisp and every other language and always will.
The point isn't to hide the lambdas, but to hide the "how", which might or might not use lambdas.
The "how" could instead open-code the procedure that would have otherwise called the lambdas; then the material just becomes embedded forms in the inline code.
Hiding lambdas with macros isn't really that common in my experience. The "how" being hidden is much more often (ultimately) IF, LET, BLOCK, and GO. Of course, higher order functions could often be used instead, but they're generally not.
https://stackoverflow.com/questions/327955/does-functional-p...
C++ since 11, C# since ~5 and Java 9 for example have actually introduced very useful tools¹ that would allow people to write much more readable code - yet there's always someone who insists that anything beyond the C++98-style is somehow evil and inherently unreliable.
¹Yes, they're by no means perfect, but they introduce are a lot of low-hanging fruit when it comes to improving code quality - if one decides to use them...
After reading I began to think about programming as an algebraic transformation from one system to another, in doing so radically reduced the amount of errors I made.
Of course, in recent times, it has become all that much more relevant to me as I began working in data science/engineering space. Even though it's not specifically about code structure for a particular language, it addresses a common flaw in most programming approaches that seem to treat all functionality as a servant of the current context which is strange and silly and not how anything works in physical information processing so why do it in code?
A somewhat common pattern that maps well to FBP is "Railway Oriented Programming." Though FBP in full takes this well beyond simply shooting errors along in sideband to the happy path.
The Description of Finite Sequential Processes http://www.jsoftware.com/papers/DFSP.htm (see e.g. the concrete implementation of the Simplex algorithm , the hamming code corrector). It shows that, with the right primitives and notation, a lot of things are simple and elegant. It's not an necessarily an easy read (depending on your math level and background), but it is a very rewarding one.
Notation as a tool of thought http://www.jsoftware.com/papers/tot.htm - a longer introduction.
Bottom line: a different take on abstraction. It makes a huge difference when you peel most abstraction layers.
I'm still trying to achieve everything he advocates, but what I've managed so far has been extremely beneficial.
This helped me break my analysis paralysis when it came to figuring out how to organize my code.
The examples are written in Java but it doesn't matter too much as most concepts covered in the book are language agnostic.
The best way to write better code is to avoid writing it badly in the first place. But you need to know how to write bad code to write code better than it. Definitely a different way of learning how to write good code, also a good laugh for anyone in industry.
It contains this gem [2], which is pretty much how every program works. I occasionally riff off this diagram for work as an inside joke with myself.
1: http://www.trs-80.org/writing-basic-adventure-programs-for-t...
I did love Design Patterns a lot though. Purely Functional Data Structures by Chris Okasaki was also really useful for Haskell, as was Real World Haskell.
Programming Prolog probably had a bigger influence on my Prolog than the other books, even though I read Art of Prolog and Prolog Programming in Depth first. Especially the latest edition, it's a really beautiful book.
[0]: http://www.lulu.com/us/en/shop/andres-valloud/a-mentoring-co...
I'm not sure I would recommend it today, but at the time I read it, in the mid 2000s, it did change my view on these "unmaintainable" technology stacks. I later came across the fusebox architecture/pattern, originally from ColdFusion - and realized that many PHP programmers had skipped some history, ending up reinventing code structure, sometimes badly.
Note that fusebox has grown and changed, I'm mostly talking about the fundamental ideas, and I don't think the later "port" to using XML was a very elegant or good idea. For those interested, see:
https://en.wikipedia.org/wiki/Fusebox_(programming)#Fusebox_... and most of the rest of that page.
It's essentially a list of anti-patterns to avoid. But crucial to it is the idea of clarity and avoiding misinterpretation by either human or compiler.
_Head First Design Patterns_ is a great influence too when it comes to OO-abstraction, for good and bad.
https://www.amazon.com/Reliable-Software-Through-Composite-D...
https://www.amazon.com/Practical-UML-Statecharts-Event-Drive...
Also, Scott Meyers's books were very helpful.
The design of the D standard library has also been very influential on my code (Mainly convincing me of the benefits of ranges over iterator)
* Fred Brooks - Design Of Design
* Fred Brooks - The Mythical Man month
* Eric J. Evans - Domain Driven Design
* Design Patterns: Elements of Reusable Object-Oriented Software
* Kent Beck - Extreme Programming Explained
* Kent Beck - Planning Extreme Programming
* Michael C. Feathers - Working Effectively with Legacy Code
* Daniel Kahneman - Thinking, Fast and Slow
[EDIT] Correct the author for legacy code
Author is Michael C. Feathers
Understanding that nothing is purely logical nor is it purely expressionistic; by pigeonholing your perception of any design whether it be Code, Math or Art to either Logic xor Expression you are blinding yourself
The other angle for GEB is design
I read GEB along with Design of Design years ago following my mentor at the times suggestion, I would highly recommend doing this. Think of it as the Gödel, Escher, Bach, Brooks (Although Brook's writing style can be laborious at times) Recognizing patterns in all design work helped my understand my own design process better.
That said, my books-to-read list is ever growing as well...
It came along at just the right moment in my career, when I was struggling to understand how I could build things in a more elegant way. I picked up the book thinking I was going to learn about some esoteric design pattern, and came away with a much better understanding of the languages I was using and all of the other design patterns I thought I had learned about previously. It's clear, concise, and focuses on concepts over specific tools and libraries, but most importantly it's practical - it has real, practical code examples and explains how to actually build an object-oriented system. It's such a stark contrast with most presentations I've seen of the Gang of Four patterns and of SOLID, which usually come with really weak examples that aren't helpful or motivational.
Everything snowballed from there. I started using composition a lot more than inheritance, I started identifying problems with side effects and eliminating them, I started writing real unit tests, I was able to better critique other peoples' code. I felt like I was finally using the tools available to me in the way they were supposed to be used.
What's funny and satisfying to me is following the author's blog and seeing that he has since moved on to focus primarily on F# and functional programming, which I naturally started to do myself after more practice with the concepts in his book. Once you start decoupling things well, and you've built a few systems big enough that you have trouble finding the actual implementation of your IWhatever and an AbstractSingletonProxyFactoryBean actually does solve your problem pretty well even as you realize the insanity of it, the encapsulating borders of classes and the need to assign everything to a noun start to feel more like a hindrance rather than a guide.
That said, I still think that most of the world's code written in object-oriented languages would be better off if everyone using them had brief, practical training to understand the value of specifying the behavior of an object through the interfaces it depends on, giving it other objects that implement those interfaces right when you create it, and doing all that creation up front (or specifying other objects that can defer that creation to later). I still see so much C# code from developers at all levels who clearly create classes only because the language offers it and it seems like the right thing to do, randomly jamming methods and fields into classes with names vaguely related to the domain, calling static methods to access databases and external services, and proudly adding unit tests for their one loose little function that mushes strings together. I push this book as hard as I can on junior devs.
Why? Because we software engineers can learn a lot from the hardware guys. Almost every piece of software I write these days contains some finite state machines (technically, every program is a finite state machine, where the binary string that makes up all your variables at a given point in time is one state, that insight alone is valuable, but I mean with explicit states in the code) - in fact, they often make up the core structure and uphold some strong invariants that make reasoning about the code simpler. And if one finite state machine does not do it, then you can nest them and keep all the benefits.
Clean code will teach you how writing 3 line functions is a best practice. It's an overhyped, overrated book which is more damaging than helpful and one of the very few I couldn't stand to read until the end due to the authors' dogmatic views.
I've read it and I thought it has some good ideas. It's the #3 most discussed book on StackOverflow. However, I've been downvoted on HN for recommending something the book talks about (comments are usually misused)
Maybe I'm missing something more experienced developers know.
I find it painful working with codebases that don't use most of the principles re:
- Write code like good stories: Code should read like a story, methodically descending the call graph method by method; each working at one level of abstraction.
For example, why is string manipulation littered all over this function that's supposed to be dealing with consolidating reports? Ugh. I like when one function stays at one level of abstraction
- Factor out conditions into descriptive methods: I shouldn't have to sit and read through 5 logical operators, some arithmetic, and method calls in 1 'if' conditional, to understand why we're doing all this.
Once again, this goes back to having your code read like a story. Factor it out into a descriptive function, so that I can understand at a glance and deep dive if necessary.
- Comments are a failure: This applies 99.99% of the time. It's almost comical when someone leaves behind a comment that could be eliminated by factoring something out into a descriptive method call instead.
There's a lot of other great stuff in Clean Code that I'm forgetting off the top of my head. (It's been awhile since I last looked over it.)
I've come across very few code bases that are a literal pleasure to read. https://github.com/jekyll/jekyll is one of the cleanest codebases that comes to mind immediately. And on the other side of the coin, https://github.com/kubernetes/kubernetes is one of the dirtiest.
If anyone wants more details/expansion, let me know and I'll reloop. (On my phone right now.)
Having read both a long time ago, I also feel like Code Complete is the one to recommend: it covers more ground, is pragmatic, and more importantly most of its recommendations are based on studies, i.e. they come from empirical evidences. Clean Code compares poorly to this, in that it's just one guy's opinion.
That doesn't necessarily mean Clean Code is a bad book, but since our work relies primarily on logic, I'd rather have arguments presented to me logically.
Since both books cover pretty much the same ground, it makes more sense to recommend Code Complete.
An example is tons of tiny methods of 1-3 lines each. Sure splitting up methods is good practice but it's painful to read code where you continuously have to "go to definition". There is an article written about this by John Carmack here http://number-none.com/blow/john_carmack_on_inlined_code.htm...
Another example is the "comment are a failure" mindset. Sure I don't need comments that say this code is doing an addition between two numbers, but a piece of code doing some complex business logic definitely benefits from a comment explaining what the hell it's doing. No amount of function-splitting and function-naming can replace such comments. Yet all too often people who read Clean Code treat comments as some sort of abominations that has to be avoided at all cost.
Please provide examples of such codebases you're talking about.
One justification of modularity is to hide details. Thus avoiding the need to "go to definition". If the tests pass and the name is descriptive, then you should understand the method without needing the details.
For example, Jekyll's Site#process [1] provides a high-level overview of how the site is built. It reads like simple, plain, easy-to-understand English. Now, I've chosen to dive into Site#write [2]; it tells me that for each site file, we're going to write it out to its destination, as long as it's supposed to be regenerated. Awesome, that's easy to understand.
Say I want to write another method that operates on all the site files. I don't need to know how Jekyll finds all the site files. Heck, they could be in specific directories... or it could pull configuration over the network... or they could be provided through command-line arguments. Who cares! I just use Site#each_site_file because it's an implementation detail.
And yes, Jekyll provides little 1-3 line helper methods like Site#incremetal? [3] all over the place to codify conditionals. These are extremely helpful.
On the other side of the coin, do you know what this conditional is for [4] in Kubernetes? I can't for the life of me understand its purpose without being forced to look into the details. It'd be much easier to read if it was extracted out into its own descriptive method such as activeMultiNodeInterface maybe? I don't know because I literally don't know the intention of that conditional. The original developer could've made their intention far more clear to subsequent developers had they extracted it out.
I find your citation of Carmack underwhelming for two reasons: (1) he's writing to a very specific target audience -- game developers, and (2) he admits himself, "The whole point of modularity is to hide details, while I am advocating increased awareness of details." Carmack is in no way supporting the idea that "it's painful to read code where you continuously have to 'go to definition'". In fact, quite the opposite, he's advocating a very specific recommendation to a very specific type of developer working on a very specific type of project. Simple as that.
You'd do best to provide some examples and empirical data supporting your assertions.
[1]: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/site...
[2]: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/site...
[3]: https://github.com/jekyll/jekyll/blob/master/lib/jekyll/site...
[4]: https://github.com/kubernetes/kubernetes/blob/master/pkg/kub...
I would assume all the best knowledge could be found online for free by now.
Hyperlinked text might be better, so that you have a graph of knowledge not a searchable seq(book with chapters and an index), but there's a natural order to learning things, the teacher hopefully has an idea what order it makes sense to put the concepts in front of you in.
tldr; books are hard to beat, if something was going to beat it for me it would be a well designed, portable wiki of the book's contents, maybe with some interactive exercises. Definitely not the video lectures and homework of most online courses.
1) It can do wonders for focus to disable your network connection, open a book, and work through examples (but maybe I just get distracted easily). This actually applies for most tasks that involve a computer and don't need the internet.
2) Not specific to print, but a book that covers a topic in a structured, beginning-to-end way can help reduce gaps in one's knowledge. I learned a bit of BASH by just googling around and reading Stack Overflow, but reading The Linux Command Line (https://www.nostarch.com/tlcl) taught me things I wouldn't have gotten in 10 years of stack overflow browsing.