A principled approach to software development

People who create theories and techniques simply give a name to what they were already doing.

Learning to categorize what you see and do has its place while learning, but after a certain level of mastery, you should leave theories, techniques, and patterns behind, and just do.

This doesn't mean you don't do the things you have learnt and value, but you don't bother with names anymore. You develop a set of principles, and you instinctively apply them in a state of flow.

Some of the principles I've come to value are:

Design things starting from creating examples of their usage

Put yourself in the shoes of the user, design the user experience, and model concepts and abstractions based on this.

Start from outside (closest to the user), and proceed inward (getting closer to the data).

TDD is aligned to this principle, but this applies even when TDD doesn't: when there's no behaviour and you're designing data representations. Tests are a great way to model usage examples, but you could do this in other ways, and it still helps.

Design with modularity in mind

You should think of everything you produce as a module.

Focus on inputs, outputs, pre-conditions and post-conditions, state, and dependencies (transitive and non-transitive). Separate the contract of your module (what the user uses, as per above), from its implementation.

Don't leak any implementation details in your contracts. Be mindful of what transitive dependencies you introduce as part of your contracts. Expose only the minimum necessary for the intended usage as part of the contract.

Keep your modules maintainable

Interfaces are your friends here, as they allow to separate what you expose in your contract from the structure of the data in your implementation. Use them not only to expose behaviour, but to return data as well.

Only test your modules by exercising their contract. This ensures that your tests will still work when you change the structure of your implementation without breaking your contract or your behaviour.

Avoid circular dependencies between your modules, including variables, functions, types (classes, interfaces, etc.), files (e.g. multiple types within a file), packages, and build modules (e.g. in Gradle, Maven, etc.).

Make it hard to misuse your modules

Create abstractions after domain-specific concepts, and put your behaviour within those abstractions. Don't use a "functional" approach by extracting data and passing it around.

Leverage types that enforce business rules as part of their construction to eliminate unwanted possibilities by using these types as inputs and outputs.

Excellence is achieved when no undesired possibility is left.

Don't be simplistic with your abstractions

Hiding your implementation details is the goal, but hiding the underlying instrinsic complexity is problematic.

An example is that you shouldn't expose a remote invocation as a simple function. Remote invocations are nothing like local functions. Hiding this instrinsic complexity makes it hard for the user to deal with it appropriately.

Design holistically

Design your system holistically. What's its purpose? What are its desired properties and outcomes? What are some different ways you could produce a system that achieves its desired characteristics?

Don't make local choices and optimizations, but consider how your choices influence the whole. Delay choosing your technologies and approaches for as long as you can.

Everything is a trade-off. Intentionally choose what you'll have to fight. The problems you don't want to have, and the ones you don't mind dealing with.

Team up with people better than yourself

Surround yourself with people that are better than you at what they do best. Great people aren't great at many things.

You want a small team of 3 to 4 people with complementary strengths, working as equals. Work synchronously together, on the same task, without separation of responsibility. Ensemble programming is very aligned with this principle.

System design requires juggling a huge amount of variables and considerations. Security, performance, user experience, reliability, maintainability, etc. You're not going to be great at all of these. By working together as a team, you can reach peaks you would never conquer on your own. As a bonus, you'll also learn a lot.

Stay lean

Avoid rules, processes, meetings, documentation, handoffs, approvals, committees, cross-team dependencies, and standards.

Be a Michelin-starred restaurant, not McDonald's. A lab, not a factory.

Most days should be entirely about collaborative problem solving and programming.

Minimize all activities that don't directly add value for your users.

Aim high

In the majority of cases, people and teams fail to reach great highs because they don't try in the first place. It's not because of lack of skills, or because the company doesn't want them to.

It's because they become complacent and convince themselves that what they do is good enough.

There is no enough. Chase perfection. Every day better. Today better than yesterday. Tomorrow better than today.

Apart from the clear benefits of learning and doing better, this will keep things fun and satisfying.

Trunk-based development, single infrastructure environment, continuous integration and deployment, and being able to test all things locally are all great examples of this.

Optimize for readability

Write your code with the explicit goal of making it as understandable as possible for the reader.

Stick to one statement per line. Name things expressively and according to your business domain. Avoid a high cyclomatic complexity, ideally avoiding any if statements.

Order the types and functions within each file according to their usage: from the highest-level abstraction closest to the user, down to the more local and narrow-scoped functions.

Stick to the same level of abstraction within a function. Don't mix high-level behaviour with low-level data manipulation. Don't mix reactive and non-reactive constructs. Move abstractions at a different level into separate functions.

Be consistent in the way you organize your services and libraries, as it'll help people to immediately understand what's going on.

Parting words

That's all folks. These are some of the principles I swear by.

Do you also use principles to orient yourself on the job? Are your principles similar to mine? Does any of my principles evoke a negative reaction in you?

A principled approach allows you to evaluate technologies and approaches based on how aligned they are to your principles. And by experimenting with things, you might evolve your belief system and the principles you adopt.

As always, let me know what you think!

Dan Partridge

IT Director | Head of Information Technology | IT Transformation Director | Working with CTO's from Fintech to Financial Services translating the tech for leaders

5 个月

100%. Designing with modularity and maintainability in mind, as well as aiming high and staying lean, are key to navigating complexity.

The principles resonate. I’m challenged with the very first sentence of this post though. The “too complex for rules and patterns.” If you mean “only” rules and patterns, then maybe.

Bogdan Grigorescu

Sr Tech Lead | Engineering | Automation

5 个月

And the one practice encompassing all: caring. Do things because it’s right.

Dan C.

Agent-Native Engineer | Building Adaptive Intelligence Systems | Full-Stack Software Engineer

5 个月

Therefore the Agile Manifesto is a set of principles.

Marco Consolaro

Consulting, Coaching & Training for Modern Software Engineering - Innovative Agile Software Technical Training Coach & Trainer| Co-Autor “Agile Technical Practices Distilled” Award Winning Book | Co-founder Alcor Academy

5 个月

Also known as "heuristics"

要查看或添加评论,请登录

社区洞察

其他会员也浏览了