The Grug Brained Developer
grugbrain.dev
grugbrain.dev
In my experience, I have to fight to keep my devs from over engineering their solutions and just get something going.
I'd love to work with a dev who's happy to think about how to most quickly deliver value and who's willing to help with the cost-benefit analysis for each suggested feature.
Problem is, for that, you need a developer who cares about and deeply understands the use case/the product. Many of the devs I've had to work with were more interested in building pristine codebases with clever abstractions and ability to scale to unnecessary numbers of users or bytes.
I'm sorry.
Now I'm right on the front lines of the business and it's eye opening. I think we need to take time to tech the domain to devs first. It's expensive and won't pay off unless the dev stays on the project for a while but it's the only way to allow the dev to understand what they're trying to do for the business.
In reflection I'm wondering if the problem is more that an external consultant is often not aligned with the business. Being directly employed helps with alignment.
Developers are on the hook for bad code and complexity. Rushed code makes feature work take longer, it makes working more irritating, and creates operational work. Everyone is burned by a team that does these things poorly at some point in their career and it drains the life out of you.
They need to trust that you'll schedule time to go back and do things correctly. Clean up technical debt, nail down all the requirements, etc.; you don't want to be jumping from MVP to MVP. Maybe you do this well, I don't know. But you need to understand the motivations and incentives of the devs you work with better or you're going to be fighting them constantly.
grug say nothing of "rushed code". Grug get sick if grug drinks from dirty water, even if it is closer than walking to clean water. Rushed code and dirty water hurts Grug.
But that not mean that Grug needs to dig a trench to clean water, or build wall like beaver to get more clean water, it hurts Grug's back. Grug just walk to clean water when Grug is thirsty.
Grug dig canal to clean water before, and Grug need to always keep canal clean and me not able to hunt for food because me digging new holes. One time, chief said village need to move to other side of mountain. Grug said goodbye to canal and my beaver wall. Grug should not built them.
Especially when you are not given the time to improve things. Worse when you're told they've negotiated 20% extra time to clean up, but every estimate gets shortened by more than 20% to please the customer.
Then you're in the firing line when new features don't ship in time and bugs keep popping up everywhere.
It makes you question your ability as a developer. Sapping your motivation to the point where you feel drained even thinking about the next task.
Sometimes I wonder how we got to be highly paid, but sometimes, completely lacking the authority to do our own jobs.
I've got a kind of fuzzy idea that this is a similar mental bias/logical fallacy to the black swan events that Taleb talks about. People assume normal distributions, but the underlying is a Pareto or Power Law distribution. They do this because it's easy and has lots of nice and practical properties. In the case of markets/finance, it makes the math really easy to work with on a day to day basis in the 99.9% case. But in the black swan event, the .1% case, it totally falls apart and your model cannot account for it.
Much like the financier assuming normal instead of Power Law, the production of software assumes the industrial model of production. This works for some high % of cases to get some amount of productivity out of most writers of software. But at the high end of software professionals, or in a situation in which something actually new is being built to solve an unsolved problem, this breaks down.
Anyway, this is not a wholly thought out thesis and mostly rambling, but it is poking at my mind and I figured I'd write it down somewhere.
I think "prototyping" is better than "refactoring". Prototyping basically means that you write your app more than once. On the 2nd round you know what needs to be there and what not. You may have come up with a much better much simpler design for the whole system when you know what needs to be there and what it needs to do.
Code-quality is important no doubt, but it is not important for code that doesn't end up in the final product.
"Just build a version of this thing as quickly as you can. Don't worry about performance or anything so much, the goal is to get an idea of what these features might actually feel like to use so this is just a proof-of-concept project"
Then "Oh hey we are going to give that proof-of-concept project to a client to try out and give us feed back. Don't worry they know it's just demo code at this point"....
Shortly after that though it's "The client wants these changes and you're who has to support it now. The client says it's slow, why is it slow? Client wants a new feature, add a new feature!"
And I'm left dealing with a horrid, awful, garbage piece of code because I'm the one that wrote it. I was explicitly told to write it entirely as a proof of concept and not as anything production ready which is why it's shit. This has happened to me more than once.
So until I have my PMs and Team lead tell me a thing is going to be a prototype/PoC and it turns out to actually be one at least once at this point I assume it's a lie
Such an approach gets you the benefits of being able to explore a problem space with code in a quick, sloppy manner, without any risk of anyone actually deploying what you wrote.
If it's not? You have a solution, so of course it's going to be deployed.
The fact that your solution is gonna fall over due to being brittle code is really unfortunate, but it was avoidable if you'd just built a prototype instead of a product.
That's when it's time to have a frank discussion about whether that's really what they want (and maybe to consider whether this project is really what you want to be working on, if it's a common pattern).
That needs to happen after feature-completeness, but before a project goes into maintenance.
PMs aren't usually accountable when their shortcuts come and bite the team further down the line. Developers feel the pain instead.
PMs won't be honest with the business that they sold an undercooked product. Need to suddenly scale up that "pragmatically" designed database? I know in my heart that too many PMs will _never_ manage expectations with their superiors if they can harangue developers into overtime, out-of-hours alerts or death marches. It’s asymmetric risk.
Don't take that personally. I’m sure you, individually, do the right thing. But my experience is that PMs / POs / team leads as a group can be bad actors. It's just the way incentives are structured for middle managers. By the time the problems of your "pragmatic" architecture have emerged, your typical PM / EM / team lead will either be working on another team, doing another job, or talking about "tech debt" as "just one of these things", a mysterious natural force, visiting your team stochastically like a freak summer snowstorm or an outbreak of measles.
_That_ is why developers are cautious. Do you _seriously_ think that you are the only person in your team who understands "commercials" or "cost benefit analyses"?
Experienced developers do, which is why we've learned the best strategy in the long run is to avoid offering too many concessions to people without skin in the game.
Good PMs are a bit of a paradox. They are immensely impactful and bring value to every project they touch. Yet they are also far overqualified for what is often a thankless job.
I also have come to the view that even good PMs will rarely allocate the work that is truly impactful. Some of the best work of my career has been when I've gone off piste and prototyped something that has surprised my managers or disrupted the way a team thought they had to work.
You can't go rogue often, and you have to expend your political capital carefully. But you will never do the best work of your career waiting for someone to assign you it in JIRA.
As someone already said, it's a thankless job and I empathize.
Signed: frequently rogue developer.
But in my experience the devs that want to /optimize/ for going rogue, are the ones you least want re-engineering your entire auth system overnight with code no one else understands and/or the most abrasive when it comes to arguing about why they are right. It's a gentle balance and being honest, kind, and collaborative goes pretty far....
I can negotiate with my PM. I can't negotiate with the bad dev who made every method call in every file depend implicitly/somewhat indirectly on 20+ things two years ago because "long argument lists = bad" but "dependency injection = good". That ship sailed and it still hurting me every time I need to add a new feature.
When I came to this realization myself, it was both sad (losing faith in the system which I had, until then, trusted to allocate work effectively) and liberating (I can just trust myself and do what I believe to be best)
In 2013 I went to a project management conference. I'd realised that as a developer I was stepping into this role frequently as I worked at a small agency, and I enjoyed stopping projects becoming disorganised messes.
Talking to PMs about what being a proper PM entailed, I heard too many say "if the project goes well someone else will take the credit, but if it goes badly for any reason you'll take the blame. And you don't have power, you can only ask up/down the organisation while at the mercy of politics".
So I decided "no thanks" and stayed a developer.
I realized this attitude is common to all tradespeople, not just developers, but also HVAC techs, roofers, electricians, pretty much anyone who's long-term accountable for supporting a large, complex system.
More of an observation than anything.
Wow, you're spinning some wild stereotypes here, and while some are based in truth (as are all good stereotypes), I'm going to take issue with this:
> PMs won't be honest with the business that they sold an undercooked product. Need to suddenly scale up that "pragmatically" designed database? I know in my heart that many PMs will _never_ manage expectations with their superiors if they can harangue developers into overtime, out-of-hours alerts or death marches.
While I've certainly seen incompetent, bureaucratic and/or poorly incentivized PMs, I don't think I've ever met one who wants to throw their developers under the bus to get a job done. I'm not saying it doesn't exist anywhere, ever (big world out there), but you've made a couple faulty assumptions:
1) A PM is not "a middle manager". It's generally a non-management position, despite the title. The classical "PM" has to use soft force for everything, and only gains authority through a history of doing the job well. We're constantly managing expectations in every direction, and "up" is just one more.
2) Even if we were "middle managers", those amongst us who have been working for a while realize it's bad practice to leave a trail of burned-out colleagues behind us, due to a history of bad decision-making.
I'll add a third, specific to my own history:
3) one reason PMs might not "care" about scaling up that database, is that it almost never needs to be scaled. Seriously.
The engineer side of my brain wants to optimize everything. The PM side is always having to remind that side that most of my engineering life was spent in a cycle of useless premature optimization. The war continues.
Anyway, it might be good to talk to your PMs and not assume they're evil villains. And if I'm wrong and you're working in a truly Machiavellian hellhole...you should look around. I hear it's a pretty good job market.
I've always thought PM's to be more or less aliens with a ray gun tapping at their watch. Communicating with them goes nowhere since they don't empathize with your point of view. The only thing they're concerned with is when something's going to get done. The only form of motivation is threat of existence in the company.
> While I've certainly seen incompetent, bureaucratic and/or poorly incentivized PMs, I don't think I've ever met one who wants to throw their developers under the bus to get a job done.
Perhaps, it's also possible you've never worked at enough shops to see it.
> one reason PMs might not "care" about scaling up that database, is that it almost never needs to be scaled. Seriously.
You're not the one going to be called at 4am when postgres takes a dump. If you volunteer to cycle into the on-call hours, I'd feel more compassionate for your point of view. Until PM's do this, I decline to see it that way.
I guess I feel like the PM is worthless position unless the PM is writing code along side the team, and can fully appreciate the technical problems -- and more importantly -- offer technical solutions.
> Communicating with them goes nowhere since they don't empathize with your point of view.
Can't speak for everyone, but again, I've been on both sides of the table, and I've never seen an example of this. I think it's rare.
> The only form of motivation is threat of existence in the company.
This is so extreme that I feel I must respond just for the sake of other people reading it: if your PM is motivating you via threats, something is very wrong.
Without knowing what your experiences are, as context, it's not possible to see the value in this. How many places (for how long) did you work as a dev?
Seriously? This is how 80% of companies work. In many of those companies, stack ranking is a very effective tool for running people out the door. It's often _baked_ into corporate culture.
I've had some problems with PM before, but never about threats, and that would go very poorly with most people I know.
Toxic Management / Toxic work culture.
https://www.cnbc.com/2022/01/14/the-biggest-reason-people-qu...
Modern management have had what? 100 years to fix this now?
I became a PM because I wanted a new challenge while not being totally divorced from making things. It's a harder job, in multiple ways, and one of the things that makes it hard is convincing (mostly junior) engineers that I know what I'm talking about. There are many days when I want nothing more than to go back to the simple, closed-form world of writing code. Compared to dealing with people, even the hardest coding problem is straightforward.
I probably shouldn't be replying, but I've noticed too many coders hearing "PM" and flipping the idiot bit. I want to do something to fight back against that trend.
It's good not to make assumptions about other people.
I was blessed to be able to build a team of developers who understand business value and prioritise accordingly, who like building things for others and not just a shrine to their intellect [1], and I wouldn't want to have it any other way. It's amazing to work in a team where most challenges are product and market challenges, and the rest is just pragmatic technical considerations. The world people describe in this thread sounds like a self-perpetuating hellhole.
[1] although arguably it's much harder to build something simple but good enough and compatible with future changes
Is exactly the kind of paternalistic nonsense that developers have to endure.
PMs exist to shield us from the shit. I work with a great one who does this, trusts the team and lets us get on with it without introducing ridiculous process, but the vast, vast majority of PMs I've had to work with are utter garbage.
Maybe when it's a large dysfunctional org, yes. Ideally PMs exist to facilitate, not to "shield".
I think you'r experience as an engineer has biased your view. For example number 3 with your engineering experience you might think that but ultimately it's the engineering team that has to deal with the scaling not you.
Your incentives are to scale everything back to meet dead lines while the engineers incentive is to make every thing work so that when it goes live they don't get called into a meeting to be thrown under the bus. As the only one that actually produces something that can be criticised at the end of the day the engineers incentive is naturally to minimise that.
The problems that arise from this are not people problems, its not a problem with the engineers spinning stereotypes or pm being bad faith its a problem with incentives being aligned agains't each other.
Except that it's not a problem it's everything working as intended, the higher ups want pm and engineering to have it out with each other as they see that as checks and balances.
Most of the things will not need to be "scaled" at all. Ever.
As an engineer, I mostly struggle to come up with a simple solution while accommodating all the crappy, leaky abstractions in the rest of the code: that's the hard part of the job. And 20 years in, I still wonder why people think it's smarter to introduce seventeen layers of abstractions for things that have one or at most two implementations, but that's what they do.
Tell me you are a Java dev without telling me you are a Java dev. :)
(While I know it's not just Java with this problem, my personal experience is that Java is the worst.)
FWIW, I am not :)
More like "incompetence" (eg poor people skills, planning skills, and often lack of technical understanding), followed by shallow human nature which gives in to survival instinct, which generally leads the PM to "blaming developers", so they they don't themselves get in trouble (and a lot of people have problems owning mistakes in general, but that's enough misanthropy for one post).
Also, they don't really _blame_ the developers by point a finger at them and shouting "he did it" like some childish display. They try to soften the blow (humans usually try to be decent; we just can't agree what decent is always) by adding an adversary into the story (like the technical debt, or hidden complexity).
Edit: Forgot to address this, but talking to a PM can often backfire, because you need exceptional interpersonal skills to have difficult those kinds of conversations with a PM. And if they're insecure and take it the wrong anyway, they can make you even more miserable.
Where this border breaks down varies from org to org. No one likes having the short end of a stick. Usually if the teams have more mixed ownership boundaries and feel "the win" of a launch, then this becomes less of an issue.
Years ago, was in a situation where I was working with a PO/PM set of folks (rotated around a bit, but a small team). Multiple times I would suggest X, and get "No, that's confusing, we won't need it, that will confuse users, we will never need that, etc". Then... they're gone, and the new replacements are asking "why don't we have X?"
Similarly, "hey, I need to do XYZ on feature ABC". Reply along the lines of "hey, don't worry - we just need the minimum thing up now - we'll revisit later. I will take responsibility if there's any fallout from this decision." That phrase was used multiple times over the 12-18 months.
Guess who's not here any more to answer any of the 'cut these corners' decisions? Yep - that product person who said "I'll take responsibility". Who isn't around any longer.
Many things that were assumed to be 'done' because they'd been discussed earlier were later discovered to be severely cut down or missing altogether.
What's strange to me is I've seen this pattern play out probably 3 or 4 times over the past... 20 years or so. I learned it certainly wasn't a one-off unique-to-personX thing.
The question of responsibility is a funny one. For every PM jumping ship and leaving developers with tech debt, there are tens of developers bogging businesses down with unnecessary complexity [1] and jumping ship with newly padded CVs. Do you think it's any better? In fact, what do you think is worse for the world?
[1] Too much complexity can kill a business much faster than failing to scale. Loads of unicorns were unstable and failing under load (failwhale!), but that's a problem that comes with customers, which means money, which means it's much easier to resolve than having no money and a beautiful scalable stack.
Developers (and people in general) are bad at prediction the future. I've seen many times that a developer are solving a problems in more generic and extensible form than required, creating a complex solution in hope that future changes will be easy. But then future comes not as it was expected and refactoring is required anyway.
80/20 for me includes not solving problems you don't have but think you may have in the future. If you need to solve X and X looks like a special case if Y class it doesn't necessary mean solving more generic but bigger and harder problem Y is a good idea.
Right now I can safely say that 80% of the times I foresee changes they actually happen. I won’t stop pre-empting stuff for the 20% I get wrong.
I've been in software engineering for closing in on 30 years (first got paid for a small piece of freelance code in 1994!). I've been doing this longer than some of my colleagues have been adults, much less been in their positions in company X. Many items and needs are not that hard to foresee/predict.
Worked short term on a financial tool - dealing with mortgages, assets, etc. Field for recording "interest rate" would not accept negative rate. I raised it as a concern. "What do you mean? You can't have negative interest rates - that doesn't make any sense!"
This was in 2018, where many European banks were starting to come to terms with negative interest rates. The company itself had funding from a large investor, and one of the stated goals was that the product was planning to be rolled out in Europe by 2020 (covid happened and I'm sure scuttled much of that).
Understanding that interest rates can, in fact, be negative, doesn't require some fancy economics degree. You could just go to yahoo finance and read news headlines that it was happening.
And... we had to create entity records representing a bank - name, address, contact info, etc. Nothing crazy, but "we need to handle multiple currencies". Someone added "currency" as the field for the bank - a one-to-one only. I raised the issue that large international banks can and do deal with multiple currencies. I got some blank stares, and a "banks don't do that" from some project lead. 2 months later, demoing for one of the VPs... a VP said "these bank records should be able to have more than one currency associated". "Yes sir, we'll get right on it!". Ugh...
But hey, I'm just a developer, right? It's better to go back and tear up months of work and tests, and push back release dates, than to just listen to a concern from someone with some experience.
"For v1, we only support single currencies. Multi currency records are on the roadmap for v2 in Q3" or "We prevent negative interest rates on purpose and will notify the user that negative rates will require a call to support team" or similar responses - both would have been mildly annoying, but I felt like I was being gaslit by folks in the company, intimating that normal/basic stuff was just random stuff I was making up.
It’s also possible I have a selective memory, but I imagine I’d be a bit different if it often came back to bite me in the ass.
But it’s human nature to protect one’s job and decision making autonomy. PMs being no exception, often underweight their roles in communication transfer and overweight them in decision taking.
1. I still want to produce excellent code that will deliver value, work predictably, be fast, and be robust against future grugs. I am driven to do this by forces I don't understand myself.
2. I also feel a deep dread of being stuck with a piece of code for any longer than absolutely necessary lest I end up trapped in it forever, so I want to be rid of this code and be rid of it now, which means I need to find a way to get it done and ship it so I can move on to the next urgent thing.
The result is that you will get a steady stream of good code from me with pragmatic and well-documented compromises, for the low, low cost of my sanity.
How to do that without being disrespectful?
How come programmers accept this kind of disrespect for their craft? Aren’t they supposed to be the masters, the Hattori Hanzos of program code?
The sound and play-ability on all of them is superb, what differentiates them is the materials and the aesthetic aspects - i.e. fancy inlays and stuff.
Similarly, I imagine you could go see a stone mason and ask for a simple brick wall for 20% the price of an ornate bas-relief facade, which would still be well constructed.
Or ask a blacksmith for a simple sword, rather than one with all sorts of shiny metalwork on the hilt.
Look at any entrepreneur luthier, however, and the story is very different. All their instruments are unique, and they may work on some complex ones for a long time before they decide it is ”good enough” for their standards of quality.
In the same sense, independent masons and smiths are, of course, more aligned with software engineering than any of their corporate counterparts—if any, because these professions are rather contracting-oriented.
Maybe seeing complexity as inherently problematic is actually a coping strategy employed by software craftspersons who have struggled with corporate demands such as inhuman work allocation and deadlines, internalised those demands as ”the way business OUGHT TO be done” (as if expecting a punishment for doing otherwise), and eventually become grugs who shake their club at anyone who triggers their corporate PTSD.
I really hope the grug meme does not become reality.
But it's only in side projects that I really feel I'm able to work as a master craftsman, without the ever-present burden to just ship a pragmatic compromise that delivers value to the business so that I can move on to the next pragmatic compromise. I wonder if this is another reason why, say, doctors working in hospitals will often also have their own private practice — sure, the extra money is nice, but the autonomy and mastery that comes with a side business might be even more important.
I've worked with my share of them, but I've also worked with my share orders who'll hack together the quickest possible kludge that meets enough of the requirements to seem to work on the surface.
Interestingly, given the choice, I'd have some of both those types on my team. I really like having the "architecture astronauts" being involved in the very early stage of greenfield projects. Their desire to push the boundaries can help ensure you start off on a great foundation. I also really like having "that guy" who can hack minified javascript and manually deploy it on prod servers to fix a critical bug _right now_ while someone else goes through the "proper process" to get it into the repo and through the CICD...
But you really really need a majority of your dev team who'll take the proper pragmatic approach, or at least who can be cajoled into doing so.
I would argue the opposite, having inherited a complete shambles. Started when Mongo was peak hype, but would be far better suited to a relational database. Lots of developer time spent to run on Kubernetes, so we can scale, even though we are in no danger of a having high volume of customers in the forseeable future (it's a very specialized domain).
In practice tho, the grass is always greener on the other side. You either wish the developer had just hacked something to get it going, when the feature failed, or you wished they had put more effort to make it more abstract and scalable, when the feature set for scaling.
Everyone's a genius on hindsight. Getting it right is more of a mix of experience, gut feel and luck imo.
The more abstract-problem/algorithmic the hiring process, the more I see this.
Ask practical coding problems instead, screen for practical devs who can find 80/20 style shortcuts.
(You probably still want some of the abstract folks too, though, to round things out.)
Engineers represent themselves. They are accountable for feature delivery speed and product stability. That being the case they push hard for hardening, automating, and maintaining.
The two have to balance each other out. PMs need to push to keep Eng focused on growing business value, otherwise the company will never make money. Eng needs to push PMs to focus on long term product delivery, otherwise customers will leave as quickly as they come, and new one's will stop showing up, no matter how pretty the UI looks.
If you had a team of engineers like you describe it would only be heaven for about 6 months in my experience. I've seen two separate teams of engineers run this way. Everything was kick-ass until it was suddenly horrible and engineers started bailing.
I'd love to work with a PM who has the courage to engage over a detailed specification document.
grug wonder why big brain take hardest problem, factoring system correctly, and introduce network call too
That made me laugh. The microservices madness of the past decade is now starting to settle down to more mature understandings, but there are still a lot people biting off large testing, operational, and data transactionality/ reporting costs.
People often don't recognise beforehand the magnitude of extra complexity & associated productivity costs.
I've seen companies architecting 50 microservices with 50 separate datastores when they actually needed 5 midi services.. Then lose half their productivity trying to work with all this.
How do you bring up concerns with that in good faith? It's so obviously terrible that I've no idea where to begin.
2022: Server side rendering using island-based architecture for client-side rehydration of interactive components
Then I did a search. Oh lord. One of the top results[0] is an illustrative guide on how to split out a Button component into an independent deployment... I can only hope that applying this on a Button is not supposed to be taken literally but I can also only assume that thousands of professional have now made it their calling to split out every component into its own "microfrontend"...
EDIT: Nope, looks idiomatic. [1]
[0] https://levelup.gitconnected.com/micro-frontends-step-by-ste...
My Company has a Cloud platform which is kinda like a marketplace and users can install and uninstall apps/services. In our case MFEs are a perfect fit.
Sure, some monoliths are harder to debug if there are multiple distinct packages that need to operate together if they are all in the same container. But fragmenting a simple set of services into a dozen microservices each in their own disconnected container is excessive too. If any of those fragments depend too closely on other fragments to operate (like, say, a timer service and a session renewal service that depends on the timer) it will easily fall over and be nearly impossible to tell why.
If you are going to shard up your application into microservices, be sure to split out your functions conservatively, so that like goes with like. It's easier to split stuff out more later than to try to split every little jot and tittle into its own dinky container. That way you don't end up with little containers of nothing wasting processor and I/O just for the sake of dogma.
Meanwhile, I feel it has just started outside SV. Is the remainder of the world just perpetually 5-10 years behind?
It's why keeping up with the latest on HN isn't essential for most of us - we've got 5 years to see if it's worth adopting :)
Are we talking about the same London? The market is still flooded with engineering playgrounds and microservices are still considered a perk and bragging point by many companies.
Finding a company with a sane tech stack that doesn't do complexity for the sake of complexity is impossible. It makes sense though - those companies already have their developers, they are happy and the company doesn't need to hire more.
> also, test shaman often talk unit test very much, but grug not find so useful. grug experience that ideal tests are not unit test or either end-to-end test, but in-between test
amazing
> big brain type system shaman often say type correctness main point type system, but grug note big brain type system shaman not often ship code. grug suppose code never shipped is correct, in some sense, but not really what grug mean when say correct!
grug need start shiney rock deposit ritual for grug similar program shaman so grug has more club. maybe some club have spike or some club have more big size. me no idea what grug need but me know grug need more.
me is mere artoo not big brain like grug but me has big sympathy for grug. like grug test shaman make artoo skeptical. like grug artoo wish spread word of grug. grug too is shaman. artoo most good disciple grug.
> grug warn closures like salt, type systems and generics: small amount go long way, but easy spoil things too much use give heart attack
Actually I need to translate a good 75% of grug's teachings to interview compatible language...
This is the key problem with complexity.
Complexity is fine if you understand it! It's when you're aware that something is complex, but you start to get these mental force-fields pushing you aware from the scary parts, that it becomes a problem.
> demon complexity spirit mocking him make change here break unrelated thing there what!?!
That's the happy case! The sad case is when you make a change and don't observe any obvious breakage, but then 3 years later you realise something you somewhat care about has been silently broken for a very long time.
The trick is to never stay somewhere long enough to feel the consequences of your bad decisions
> grug not say no improve system ever, quite foolish, but recommend take time understand system first especially bigger system is and respected code working today even if not perfect
grug read this and grok spirit, grug enlightened. master not club grug so much.
also why grog keep putting state in mutable instance properties on acting class when can just be parameter arguments? save so little typing but will for get curse. grug remembers making big object-oriented python2 app multi-threaded. and all that c# when in consulting on legacy codebase. grug avoid such pattern now.
My favourite part.
grug know debugger good but grug often realize grug no need debugger on smaller cases and only run it when grug need it, grug try simple code like print and log first, if grug sad and no understand grug go for debugger
So yeah console.logs are still quite common even if you have a great debugger because it’s the most accessible and laziest option.
But there’s something very rewarding about getting really good with a debugger and figuring things out quickly.
But you definitely should not be doing that if you have a good debugger as It's faster to click the line to add a break point and press play. You can SEE the state. And you can view other variables at the same time if something's looking whiffy, usually by just hovering your mouse. Plus see the entire call stack.
The thing that's boggling my mind about this is that if you know the line to add a log statement on, you know the line to add a breakpoint on. It's so much easier to just add a breakpoint.
In some languages if I saw someone adding a log statement while debugging I would immediately classify them as a junior programmer and start teaching them how to debug properly.
Either you are using a shitty language with a crap debugger or you need to learn how to use your IDE.
I use debugger all the time when I run into pointer related issue, or some checking some tensors in deep neural nets etc.
In some cases, I throw debugger just to see what is going on.
However, I have had few cases where debugger slowed me down. If you are doing something in graphics that requires you debugging issues that spans multiple frames, sometimes it's easier to notice the value over a period of time and see why things are behaving that way. From there you can reason what might be causing the issue. It can be done frame per frame inserting multiple breakpoints, recording them and viewing them accordingly! However, I prefer simple logs in such cases.
I have used both approaches as time demanded.
As for your point about logging being a fail condition, I was working on a distributed system where I had no control over which instance my code was running on. I would attach a debugger and make the same request a dozen times before the instance I had attached the debugger to processed the request. This wasn't a system I could setup a local instance of. I also couldn't reduce the instances to a single one because there were other devs, testers, data engineers working on it and the system did raster processing that regulary took 1-5 minutes. I resorted to log debugging.
> Thus, I fully support high-level languages in which pointers are hidden and types are strong and the declaration of data structures does not require you to solve a syntactical puzzle generated by a malevolent extraterrestrial species. That being said, if you find yourself drinking a martini and writing programs in garbage-collected, object-oriented Esperanto, be aware that the only reason that the Esperanto runtime works is because there are systems people who have exchanged any hope of losing their virginity for the exciting opportunity to think about hex numbers and their relationships with the operating system, the hardware, and ancient blood rituals that Bjarne Stroustrup performed at Stonehenge.
The alternative is setting (sometimes many) breakpoints in different places and remembering what happened at each of them. Personally, I prefer reading a log.
If I want a debugger and see e.g. the complete variable scope at a breakpoint, I'll use a debugger. If I just want a very quick sanity check I'll use a simple print.
Once you enter debugger land you're there. When I'm currently not in the debugger but rather compiling and executing "for real", it seems more straightforward to me to not enter debugger land when I really don't need it. Personal preference.
There are scenarios, where 2 print statements can tell you more quicker, than breakpoints. You have to step through the breakpoints, right? 2 print statements in different parts of the control flow can tell me everything I need to know at a single glance.
This seems to be a common misunderstanding, but debuggers generally also support logpoints (print statements that you can add/remove on the fly without having to close, compile and restart the application).
I do like to step through code with a debugger when I need it. But that's rarely the case. The usual bug-hunt is something like: Do we go down path A or path B and whats the value of C before and after. Ideally, there is already logging in place that gives me exactly the info I need. If not, to me adding 2 prints or expanding the log usually seems just way more sane than spinning up a debugger, when I'm already looking at stdout/logs and I just need a tiny bit of additional info in there. Maybe I need a faster machine, lol.
Tracing is also a great tool, imho.
or you are working in a domain where debug builds are intolerably slow.
Ah, the awkward "tween" stage of development. If you don't switch to the management track, you'll skin your knees enough times to learn when you need one and not the other.
The best systems are the ones that log just enough, but no more, whatever that means can be difficult to quantify. I find logs that are informative or actionable to an operator are the most useful, if a log entry appears more than once a minute and this entry is only useful for a developer of a particular submodule, it’s too chatty and likely an indication this module is not understood enough. If a system is doing its job it should for most of the time not say much. Getting to this state of log nirvana does however, as you say, require the best of developers.
Is frontend web development more complex than it needs to be? If so, how?
Oh, you're serious? :D
There's a reason most people don't, however.
some say better than no castle. grug say why building with no-no soft serve in first place. (grug know answer: more shiny rock)
But the team that uses npm, babel, webpack etc will crush you both on development speed and stability.
Just `import` and done. No build steps.
Where I find vanilla JS struggles is (for example) rendering a big tree of data, and then needing to update some data dynamically within the tree without re-rendering the whole thing. You end up with some horrible queryselector hell, or keeping some immense table of pointers to the elements. Fortunately for us, we have some tiny libraries like lit-html that can help accomplish this. In the theme of grug I think the ideal solution is somewhere in between.
Like I said, stability.
If vanilla JS checks off all those boxes for you, that’s genuinely fantastic and I’m happy for you. And a lil envious tbh. But there are a myriad reasons I/we currently can’t justify ditching the build toolchain, and most of them relate to scaling in a way that fulfills our requirements. I can’t imagine I’m alone in that.
The non obvious ones: Without babel you are either writing legacy JavaScript (which is arguably not as clean and easy to read), or users will complain your site is not working on their older browser.
Also jQuery becomes slow and unmaintainable very fast, especially considering that modern frameworks do a lot of tricks to increase performance, like detecting a row swap in a list.
Next gen frameworks like Svelte and SolidJS managed to avoid some of that because they compile to a minimal, Vanilla JS app which is easy to read. Especially Svelte at least gives you a decent stack trace.
I don't miss it though. Especially enterprise apps based on this model are just hell to maintain.
_THEN_ I want developers from that company to share their opinions about how they do it. Do such companies/products even exist? Software is so bad these days that maybe no existing software lives up to our ideals.
Methodology stuff sometimes comes from people who don't seem to code much at all, or haven't for a long time. I don't really read those things often and when I do I tend to skim them out of boredom.
Software design, architecture and paradigms are a mixed bag. There is plenty of distracting stuff there, and much is from people who again, don't code. But there are a bunch of rare gems in this area, even coming from academics/teachers or veterans who haven't been in the trenches for quite a while, but have plenty of useful things to say.
Beware of survivorship bias.
For every successful company using a technique, there might be 10 others using the same technique but running into the ground.
And not sharing the embarassing failure.
Microservices strike me as a pertinent example. It might fit huge companies very well, but not if your customer base might as well be served by a cheap Raspberry Pi.
> I want to see a company deliver high-quality products with very few bugs at a fast cadence, and continue to make major changes long into the future without slowing down.
you want eat cake and have cake.
This selects for complexity, not finished product.
We've seen a lot of one-man or small-team companies do some pretty amazing stuff, because usually they are solving a problem that they have. If you're high on VC cash (or are trying to get high on VC cash), the problems you are trying to solve is more marketing than technical.
Jeff Robers and Casey Muratory used to do a podcast called the Jeff and Casey show where they'd occationally discuss software products shipped by RAD.
One such episode details the maintenance of a garbage collector RAD shipped, which Jeff denounces as way too complicated and (IIRC) not worth the developer time or CPU cycles.
Complexity bad.
I think maybe Reaper fits the bill. http://reaper.fm/
As far as I can tell, it's two people.
sometimes that bug free thing that just works and was written all by one guy is just a real pig behind the scenes, but hey, that guy always knew what not to do, no matter how messy the APIs.
> grug tempted to reach for club and yell "big brain no maintain code! big brain move on next architecture committee leave code for grug deal with!"
> grug tempted reach for club when too much agile talk happen but always stay calm
> type systems most value when grug hit dot on keyboard and list of things grug can do pop up magic
> type systems other most value when grug make wrong, but no user see because big red arrow point to first.
I think this requires a big shift in community thinking. Programmers are trained in universities to throw OOP principles at every problem, and the DRY principle has deeply taken hold. Obviously both have their place, but they often used overzealously and come at the expense of readable and maintainable code.
All that does is move somewhat messy language constructs into somewhat messy data constructs.
The data is static-ish, which is nice. But the associated code has exploded in complexity and with XML particularly you can't even be sure if it's truly compliant - especially if it's coming from an outside source.
So I think that whole talk is spectacularly missing the point. The idea is good but functional/read-only doesn't solve the problem.
Because some problems are just hard, and no amount of functional twiddling makes the hardness go away.
Names are hard. International localisation is hard. Dates and calendars are hard. International holidays and special dates are hard. Addresses are hard. Timezones and time systems are hard. Phone numbers are hard.
There is no trivial solution to any of these problems.
The most effective solution would be an international standard collection of algorithms and APIs baked into identical standard libraries for each mainstream language.
What you get instead is a lot of people wasting time and energy solving these problems over and over - badly.
And in fact XML, JSON, SQL, etc are similar. There are countless libraries and frameworks for managing these, and they're all slightly different and likely to fail in different ways - not just in different languages, but in multiple competing frameworks for the same language.
The problem isn't the code, it's the culture around it. Instead of solving a problem completely and standardising, the industry is addicted to nearly-but-not-quite solving the same problems over and over.
That's where a lot of unnecessary complexity comes from.
I am lacking context, but SQL was made for data.
1. big brain write code when no idea big picture. later problem make grug have to rewrite many code, make grug want break keyboard on desk! (no idea where club, maybe left bathroom stall)
grug now make diagram first, tell purpose code. maybe take long time and grug no happy. but diagram tell many story from tiny picture. all see picture, all grok. junior grug see problem, ask question, all work together solve problem. many pat on back for junior grug, maybe give bonus shiny rock. then grug make code. less waste time throw away code not make sense, more grugs make code from picture same time. think of artist make mural with grid. grug happy when think code like art.
2. grug also told to refactor sometime, not happy. executive vp say refactor whole app, grug raise club very high. grug boss say "Ok, we'll do Embrace, Extend, Extinguish", grug lower club a little.
grug put up app facade, make feature flag and new feature, push feature to prod, test new feature with little real traffic using flag. test more and more traffic. when new feature use 100% traffic in prod, then remove old code and flag. do again until all old code replace. refactor slow but not make grug work overtime from bad deadline crunch, waste less money when refactor no work, less smash boss when no stand up to executive vp.
This resonates with me
If you can't read grug English, you will find it hard to navigate in the global workforce.
(Film Crit Hulk released a book; half of it was in all caps, the other half was the exact same text except in normal sentence case.)
I'll keep going if there's positive feedback, delete it if the author doesn't appreciate it.
Translated as best I could
I am keeping that.
grug wonder why big brain take hardest problem, factoring system correctly, and introduce network call too
seem very confusing to grug
>
Yes.
beautiful...well done....
Also, grug not know if big brained or grug. Grug think grug but not see big brained. Big brained think big brained stop big brain and become grug. When stop think grug big brain, big brain grug return. Hard, and bore. Life such.
Now sleep, soon shiny rocks collect.
In general, explaining without managing complexity is easy and makes you look smart. However, explaining so anyone can understand is very hard. Grug author good at this
grug relate. other day grug forget Angular have pipes even though grug use async pipe in same PR.
This is particularly concerning for me because not being too strong in the klaka klaka klaka 600LoC daily department I always relied heavily on my knowledge. Is it a coincidence that this started happening after exactly ten years of commercial experience?
grug brain tip 2: use grug-brain-compatible password generation algorithm. Grug only need remember algorithm
Example: f("hacker news") = "GRUG<3CLUB" + "hand" + "neck"
grug invent first part with number and symbol one time. rest change depending on site
When reading it, I felt like it was loosely inspired by A Philosophy of Software Design by Ousterhout. And it was! Near the end it is listed as recommended reading. Cannot recommend it enough.
I love it.
grug admit confusion is puzzle. grug blame complexity for confusion. grug make better relation to grug's target. grug reduce complexity and confusion. grug win woman and shiny rock.
"(best grug brain able to herd multiple big brain in right direction and produce many complexity demon trap crystals, large shiney rock pile!)"
</grug>
maybe grug try drink elixir?
I thought grug would say, "grug used to use debugger but now grug stare at code until grug understand code - debugger bad brain drug for grug".
but world is complex, and babytalk is denial
Who is this person? They must be a very senior Jedi
> grug make htmx and hyperscript to avoid
This is a big part of the general career skills I teach interns when I'm mentoring. Learn how to not lose the game of musical chairs. When a project is clearly going to immanently collapse under its own weight, change teams before it happens.
https://jonaquino.blogspot.com/2022/06/grug-brained-develope...
However, what I _REALLY_ want is a sw-dev (let's just say it like it is _PROGRAMMER_) version of the BOFH stories.
> given choice between complexity or one on one against t-rex, grug take t-rex: at least grug see t-rex
What could be complex to some could be simple to others.
How could grug developer possibly make sense of such a contradictory statement?
My name is Groot!
I skipped most of this post, but the combo of name and writing style seems like a homage.
think hard about think soft ==> no need think hard all time
There are two general ways of approaching software design (and I'm paraphrasing Tony Hoare here):
1. You can write software so simple there are obviously no errors
2. You can write software so complex there are no obvious errors
One thing that escapes "grug" is that achieving 1. often requires more sophistication than their magical club allows. Most well-intentioned "grug" developers will write software so simple that it becomes it's own form of complexity: a giant mud-ball of for-loops, while-loops, variable assignments, and other wonderful side effects. Instead of addressing complexity head-on with abstraction, "grug" will beat "galaxy brain" over the head.
What grug fails to understand is that simplicity isn't easy or familiar. It doesn't mean "sticking to what you know." If often requires being able to reason about programs and to verify that reasoning!
But go ahead grug... keep beating people over the head with the club and hope that the complexity will go away.
however!
beware apply advice put form data from web page into database with many layers abstraction not needed! grug see many time!
fear of looking dumb (FOLD) great danger in such conversations and beware!
Hah. Related, i became disgruntled with my previous favorite language for this reason. It promoted this style of development. Surface level simplicity built into the language, with minimal tools to actually manage the complexity that is inherent to the problem space you're working in.
Python? Java? C? Assembly?
When I switched my focus to Python, everything was more effort but I could also do _much_ more with it.
For which language is that statement not true ?
I will say someone here was right though. :)
1 could be 50,000 lines, 2 could be 500.
You hide the details but you never forget they’re there, lest they spoil and start smelling really bad.
(Also, apparently the MOV instruction on amd64 gives you Turing completeness as proved by that crazy compiler, so gotos may be made of MOVs sometimes, but meh)
And, to be brutally honest, as much as I love those functional combinators, first-class functions, streams, etc, they suck to reason about.
Sometimes loops are better!
”Simple” code is simple to reason about, but it is not as expressive.
”Complex” code is just as simple to reason about, but being more expressive, it requires more intimate knowledge of the language features and abstractions used.
Then there is ”bad” code, which is confusing for reasons other than domain complexity.
Sometimes complexity just gets handed to a developer even if they say no, and that doesn’t make their complex code bad code.
> Then there is ”bad” code, which is confusing for reasons other than domain complexity.
I've previously seen these two summarized as "the difference between complex and complicated".
> Sometimes loops are better!
That I think is backwards. A loop could be doing literally anything - it probably is futzing with global variables - so there's no way to reason about it except by executing the whole thing in your head. A map (or mapA) or a fold (or foldM) or a filter or a scan is much more amenable to reasoning, since it's so much more specific about what it's doing even without looking at the body.
I find this has good readability and by containing the mutation you can reason about it “at a distance” quite simply since further away it’s for-intents-and-purposes pure code.
What you might lose are the compiler-enforced guarantees that a functional language gives you. Some languages give you the best of both worlds - with rust you could put this in a pure function (immutable inputs, owned outputs) and the borrow checker even reasons about things like this within a function body.
And that's what map() basically does.
The opposite is true in my experience. Loops working over variables local to a function is a lot easier to reason about then a series of intricately intertwined lambdas, if they share state. If everything you do is functional then a series of pipelines might be alright, but it might not. If the functions are spread all over the place it can take a larger amount of mental load to keep all the context together.
Code should permit local reasoning, and anytime that is obscured, rather than helped by, abstractions, we incur additional cognitive load.
I mean accessing a mutable variable from a lambda is obviously insane, agreed. The whole point of using the functional combinators is that you don't do that.
When you are forced to use some accumulating global state, that leaves you with writing in a different style--loopy, if you will--which maybe is a good signal to the reader that something is weird or different, but then again maybe it isn't.
One thing that bit me using Java streams recently is that it completely broke down when I had a concurrent data structure that needed to have the coarseness of locking tuned for performance. Laziness and functional streaming operators had to just go out the window to even make it clear where a lock was taken and released. So loops it was.
Well yeah, that's very much expected. If you're even talking about locking, stream transformations aren't a good fit (except maybe if you do the microbatching/orchestration style where your stream transformation steps operate on chunks of a few thousand items - and even then your locks should be scoped to a single transformation step).
(Now I'd happily claim that for equal developer time one can usually outperform a locking-and-mutation implementation with a stream-processing implementation - not because the low-level mechanics are faster but because it's easier to understand what's going on and make algorithmic improvements - but that's a rather different matter)
They're dead simple, there's no abstraction, its just loops and variables (and state, which is the real killer). But they're impossible to reason about as a result.
You can write bad code in any language with any constructs. The more code you do write the worse bad ideas just metastasize like cancer.
That single grug got it done in ~1 month for basically nothing, and without the multiple AWS service overhead it ran much faster, fewer resources, and dead simple to maintain. Bigger company bought the smaller one, then proceeded to toss the grug code and continue the big brained approach, as far as I know never reaching parity.
But there were cool network diagrams, data diagrams, and all sorts of new, interesting, and complex technology developers enjoy playing with.
I’m more inclined to side with grug now.
Only because i have run into weirdness with printf calling malloc, back in the day. Even hello world makes me a little nervous about those claims.
But I'd love to see a sample with explicit assertions about the environment, so I could be sure there are obviously no errors.
If we are talking about coding style, then the things you have identified as actually not simple are by definition not what the article favors.
And both are neither here, nor there, regarding what the article talks about, which is basic advice that always holds: avoid complexity, say no whenever possible, opt for 80/20 solutions.
>One thing that escapes "grug" is that achieving 1. often requires more sophistication than their magical club allows.
Which is irrelevant to grug's point, as he doesn't pretend to advocate achieving 1.
the argument made in the piece is more nuanced than that. The author points out that you often cannot address complexity head on (in particular not with abstraction), because you don't even know what your complexity looks like, as the author says, complexity isn't trivial to see.
This was the old problem of inheritance as a paradigm which tried to anticipate structure of programs and code when often you can't anticipate what shape your program is going to take, often leaving you in dead ends and wrong hierarchies or taxonomies.
The author isn't saying to not abstract at all but to not do it early. Casey from Handmade Hero had a similar style he called, IIRC 'compression' based programming, implying that you write your code, and as you go over it again and again you see where duplication pops up and only then you factor it out into some higher abstraction.
When programs are defined in terms of operational semantics, that is procedurally, it must still be reasoned about in some way if your goal is to write a program so simple there are obviously no errors.
Patterns of indirection are not abstractions. It’s a fine practice for controlling indirection in code! And highly effective. But it’s not what I mean by abstraction.
Abstraction enables new layers of semantics by completely encapsulating complexity. Think of function application in almost any procedural language that is compiled to some target machine language. The compiler has to generate a lot of code in the host language to manage memory segments, return pointers, etc. As the programmer using the language you don’t even think about it at all.
One doesn’t arrive at such an abstraction by writing a bunch of spaghetti code and encapsulating common patterns.
And that is often how one arrives at simple code. It’s not “simple” because it doesn’t require anyone to learn anything or work hard to understand it. It’s simple because once the abstraction is established and proven you’re free to think about bigger, more interesting ideas.
No you don't get an abstraction like the one you described. But you often can hide a huge mess of complex code behind an interface and make it simple for your part of the system to deal with it. e.g. Think of the interface to a search engine or a machine learning system.
> It’s not “simple” because it doesn’t require anyone to learn anything or work hard to understand it. It’s simple because once the abstraction is established and proven you’re free to think about bigger, more interesting ideas.
In many systems there aren't any more interesting ideas and in that case making an abstraction where you work hard to understand it is a liability. People don't have time for that. People particularly don't have time for changing it when the outside world changes such that the abstraction no longer fits. And the outside world changes quite frequently.
Maybe you use a 10-year-old, constantly maintained and thoroughly tested library to do 90% of the work instead of writing everything yourself.
Would it be faster to compute some end state without intermediate calculations? Probably. But how about we just spin a loop forward instead if it's more accurate that way and easier to understand.
What if we cached values, then made sure the caches are always in sync? That should speed things up. Well, maybe we'll write it without caching and see how that goes first.
How about an intricate system of locks and serializable transactions so that multiple requests can run at the same time? Or maybe we just queue things up and run them one at a time.
Nothing to do with nested for loops.
Those are fine things to value even if they are highly subjective.
You might think it’s easier to read a while loop with a handful of mutating variables and a few branches. I might disagree.
I think it’s easier to read an accumulating function that uses traverse.
It’s wishful thinking that we should both hold the same values or that only one of us is right.
> big brain type system shaman often say type correctness main point type system, but grug note big brain type system shaman not often ship code. grug suppose code never shipped is correct, in some sense, but not really what grug mean when say correct!
This big brain ships code every day.
Also streams big brain work in big brain language once a week to show people that it’s not the work of big brain shamans.
Big brain is happy to answer questions and help people learn.
I very much prefer to work in a codebase of poorly written loops and variable assignments rather than one with poor abstractions.
Poor abstractions can harm way, way more than spaghetti code. Their harm usually spreads out through system.
Imagine this:
* 1 single poorly written function which takes a number of inputs and has an output, but internally it's long and messy.
* A bunch of abstracted entities, which interact throughout the system, but are poorly designed.
The complexity of 1 is isolated to the implementation. There's a nice encapsulation. Removing/Rewriting is easy.
But 2 becomes a complete mess which is not isolated. You'd have to skin it layer by layer. Thats way more scary to me.
Woah woah. This is literally the kind of complexity grug wants to avoid. Simple doesn't mean using the fewest language features or keywords, it means simple to read, follow along, reason about, and debug. Abstractions can aid in that (like using a third party library!), until they are imported/implemented aspirationally and thus often unnecessarily, and sometimes resulting in abstractions for abstractions, selective application of the abstractions vs fresh implementation, etc (...and thus AWS)
At no point does grug argue that you should stick to what you know, he just says you should trap your complexity in crystals ie literally suggesting to use the Good Abstractions, When They Make Sense.
Sadly it’s often difficult to argue with someone using the complex=bad-club, because everybody knows complexity must be avoided - at all cost!!
What one should understand is the essential complexity of the problem, then design your solution around that, to control where the complexity goes and to not introduce accidental complexity. Blindly going complexity=bad will result in it popping up elsewhere later.
Many people as you say don’t even know what it means and use complex=bad for all kind of things they disagree with, including choice of tools or libraries. Using a library you don’t know is not complex, it is difficult, also something that should be avoided but that doesn’t make it the same thing. (Adding libraries and mixing competences can of course add complexity also, but let’s not get pedantic…)
[1] https://preview.redd.it/g1lpdmt1iiv51.jpg?auto=webp&s=a5f27c...
You can reduce the latter, but often you can't reduce the former. That's why it is best to keep abstraction for code architecture at bay.
Unfortunately, in some cases complex programs adopt fundamentally different architectures (dynamic dispatch and plugins, different data structures, extra modules like Harfbuzz and Pango, etc.) from simpler programs. Perhaps they have reasons for doing so, but in practice I find myself still confused even if I understand the simplified concepts and can write useful code using them.
Does this style really appeal to anyone at all?