Procedural programming is the first programming paradigm I learned and felt most comfortable with. Even as I learned functional programming, my code has retained a very top-to-bottom, declarative structure, which is limiting in multiple ways.
I'm moving on to object oriented programming role, but still resisting the urge to default to procedural. Even with solving code challenges, I've noticed tendency.
What are some ways to break out of this habit?
Top comments (9)
It's easier if you're in a language that encourages functional thinking while being around developers who think that way too. I'm around Go and Python devs from a variety of backgrounds. The Go developers you'd think would be Functional minded, but they're basically "not OOP". The Python devs are either heavily procedural with an occasional OOP developer. Again, some of the younger devs or those not from a Java background just follow the procedural style because "it's easy, reads well, and it's obvious where the error is".
You teach them something like returns in Python, or suggest they try Rust for a code challenge, and they start embracing the "expression" mindset and start making their code more railway oriented. It's hard, though, because both Go and Python encourage the imperative flow. Even JavaScript with it's Promises does.
So it's hard, but easy to fallback into a language that allows it and is built for it. Harder still to deviate when you're on a team that does it. Even in JavaScript, though, if you're on a team that embraces it, it's much easier, like say a newer React team that's fully onboard with Hooks & Lodash/Folktale, etc.
... that said, I whip out
let
when coding Elm, so not sure 😅...I worked away from the code on modelling the problem to be solved, understanding the domain and the humans that do/will interact with the software.. Then on returning to the weeds, I had a much better view of why code should be structured in particular ways, and when procedural logic was appropriate (it often is for completing well-defined business tasks - do not throw process/procedures away!) or when a more flexible, isolated structured helped with uncertainty, at the cost of complexity of course :)
This is a great and thoughtful approach. I will try spending more time away from the code, rather than just diving in. When working in a much larger codebase, how do you factor that in your solution design?
Welcome to re-engineering legacy products :)
My preferred approach, having got a decent understanding of where behaviour is well-defined, and where it might need to adapt, was to identify boundaries in the existing codebase/deployment (processes/VMs/etc.) architecture where the behaviour is contained (if anywhere!), then isolate those through application of the strangler pattern, to draw out a set of behaviours into a modular feature that can adapt more independently. If behaviour that requires change is very distributed, then we worked on drawing that together first, by applying facades / anti-corruption adapters in areas that connect to the behaviour, then making the changes to the behaviours as required (now in one place). This is an application of Kent Beck's 'first make the change easy, then make the change' :)
Guess what, procedural is like actions... the physics, of particles flying around, or how they interact, due to some electrostatic force, and so forth.
When it is object oriented programming, it is like material, together with the actions. So you can say
or
You are packaging the properties (states) of the material (the x, y coord, etc) together with all the actions it know to perform, into a specification, called a "class". The class is a blueprint, and when you instantiate the object, the object is born. It has properties, and it has all the "methods", or actions it know how to perform.
So the actions are still procedural.
In some situations, your "world" is just a number or a few numbers, and your just write everything procedural, because there is no need to "package" the states and actions into an object. Your program is the "world" and is the "object" already.
When you write programs, a lot of time you are concerned about the actions... and about packaging these actions together with the states into an object... that's not as important and it may depend on the situation. I saw people writing a function by write a class and all it does is one method, and so he either use it as a class method or instantiate the object and invoke
obj.provideService()
and it is kind of silly.Note that the "action" is the method. In Smalltalk, you send the object a message, such as you tell the animal to giveSound, and the animal perform the giveSound action... so in some language, it is "sending the object a message". In some language, they call it "invoking a method on an object", and these are very similar ideas.
You mentioned when solving code challenges, you write it as a procedure. That's actually quite natural. For example, if given an array, and they tell you to write Quicksort, then you probably are not going to create a "Quicksort" object that can sort an array. You are writing the action, so while you can add a method to the array class such as
array.quicksort()
, you wouldn't usually write it asquicksort.sort(array)
withquicksort
being the object or even the class. You might write it as a library or module and useSort.quicksort(array)
withSort
being the class andquicksort
andmergesort
being the class methods. But it really depends on the situation. For many coding challenges, they want to see your algorithm skills, so it is natural to write it as procedural or functional.Thanks for the detailed response! You're right that may be the case with code challenges. But I noticed when working with my team (in an older codebase), it was challenging for me to write code within that context, especially when it to using fields and properties, extending other classes and working with arguments
I wish I could share the code as an example :(
I try more and more to start writing the higher level functions and/or top-level call first. So you force yourself into writing what your code should look like in the end, rather than where to start. And then you write the functions you would need in reverse order, writing the simplest, low level-utilities last rather than starting with state variable and mutating them little by little.
It's actually not that hard and I find myself writing much cleaner code from the start. If you want to test your higher level functions, just fake the lower level one to return basic string/fake object etc. before implementing real logic there. You might need to design stat beforehand too:Â pen and paper is still great for that :).
thanks for the reply! Think I understand what you mean in terms of code challenges. But how would this work with a larger codebase with a language like c#?
I don't try to change my habit.
I just find out that functional programming can reduce my code a lot. So I use whenever I can.