Learning Fluency, by Sara Simon is a really interesting article and you should definitely read it. It’s kind of hard to summarize, what I got from it was that there are a lot of parallels between learning natural languages and learning to program, one of which is that rote memorization is both really useful and really underrated. Variables and for loops are really boring for experienced programmers (which I’m suspicious is why ), but just like it’s really hard to tell someone how to get to the grocery store in a new language until you’ve memorized enough words, it’s really hard to build a program before you’ve memorized enough programming constructs.
Another good point Sara makes is that not everyone learns the same way. Some people are happy to jump into a project and learn as they go, and other people need more structure to get started. That doesn’t make anyone wrong, but it does mean we need resources for more than just one kind of learner.
Learning Fluency is a bit of a long read but it won’t take your whole lunch break either.
You might have guessed from my extremely wordy blogposts that I like writing as well as programming :) Aside from having a personal interest in writing, I think there are some really useful parallels between writing and programming.
Let’s talk about one of them: separating writing and editing. This is a pretty common piece of writing advice, I’ve seen it all over the internet.
More specifically, what separating writing and editing means is that when you start a piece of writing, you don’t edit, you just get your ideas out onto the page. Only after you have a complete first draft, whether that’s a blog post or a novel, do you edit it. Editing can be as simple as fixing typos or as in-depth as moving whole chapters around.
Why is editing while you write such a bad idea? We’ve all written essays for school and edited while we wrote and we survived, right? Well, we did, but we also put in way more effort than we had to. It is totally possible to edit while you write and turn out a perfectly good school assignment, or even a professional article or book, it’s just way more work than it has to be to do it that way. It takes for-freaking-ever too and we’ve all got other things to do :)
Separating writing and editing makes you way faster. Yes it works better if you have a little space between the writing (drafting/outlining) and editing steps, but you’ll spend less time altogether on a piece of writing if you put a day or two between those steps.
But why is that? Writing isn’t that hard, and editing isn’t that hard (to be fair, I’m saying that as someone who has always had an easy time with writing), so what’s the problem?
Part of it is that people just suck at multitasking. Fiddling with your words is a very different task from getting them down in the first place, but I think is the real issue is that editing and writing at the same time is mixing two steps that need to be done one after the other.
It doesn’t actually make sense to worry about how you’re saying it before you’re done sorting out what you want to say in the first place. That’s just going to make a mess.
Okay great, how does this relate to programming?
Programming is in many ways a very similar process to writing. You need to know what you want to do before you can figure out how exactly to do it. Programming is certainly more rigid, with writing you can still make yourself understood with a misspelled grammatical trainwreck, but it’s not so very different either. You can start a piece of writing and know just what you want to say and then discover it just sucks and you have to scrap it and start over.
What you want to do with a program can be more tightly constrained by what’s feasible given the existing system/libraries available/limitations of the language you’re using, but in some ways that makes it easier because there’s no debate over whether your program works or not, you can just run it and find out.
When you’re writing code it’s very simple to separate writing from editing: write pseudocode first. Figure out what you want to accomplish (yes that’s a big enough problem for many books on its own, but that’s another blog post) first, then break it down into simple steps, then start implementing it after you know what you want to do. It may turn out that there’s a technical reason your original plan won’t work, but you can still iterate on that and make a new plan before trying to implement that one.
If you find yourself stuck on a problem, you might be trying to do too many things at once. Try separating what you want to accomplish from how you want to do it.
I’ve been talking about code smells for a while, but code is far from the only thing that can go wrong. What about process smells? There are things in your development process that may not necessarily be wrong in every situation, but are a signal that something could be wrong. Some of these are more definitely stinky than code smells, but not all of them. As always, if you’re happy and productive with the way your process works, stick with it!
Here’s an example of a process smell: Friday prouction deploys (in case that tweet ever gets deleted or something, it’s a picture of a jack o’lantern with the words “Friday deploy to prod” carved into it and the caption “Scariest Pumpkin”). Friday deploys are almost universally a terrible idea, which makes them an especially nasty process smell, but once in a while they are actually a reasonable choice. Usually when they’re the least terrible of a set of bad options, but sometimes bad options are all you have.
If you’re fixing a severe bug that prevents a lot people from using the system at all, then it makes sense to deploy whenever you have a fix, even if that’s a Friday, even if it’s late in the day on a Friday. And if you have the unusual luck to have a dev team with staggered schedules who are all familiar with the code being deployed, then no day is really any riskier than any other (is this actually a thing? I included it because it’s technically possible but I’m not sure it actually happens anywhere).
If you work in a very rigid environment where nobody uses the system over the weekend at all ever, then knock yourself out! If no one touches the system over the weekend, then you’re not going to hurt anything by deploying on a Friday.
Most of the time Friday deploys are just a bad idea. Why?
Because no matter how great your QA department is, even they can’t test every single possible combination of weird commands and bad data and dodgy wifi that your actual users or customers can, which means things may break in wildly bizarre ways when no one is around to fix them. That sucks for your customers and sucks for your support team and sucks for your revenue and sucks for your reputation and sucks for your stress levels when you come back and discover that everything has been on fire all weekend. Oh and it sucks if you get called in to fix it when you thought you were going to have a nice relaxing weekend. Spare yourself and the rest of your team a lot of hassle and don’t deploy on a Friday (or late afternoon any day) if you can avoid it.
This process smell is really interesting if you keep in mind this definition of a code smell, by Martin Fowler:
A code smell is a surface indication that usually corresponds to a deeper problem in the system.
If you’re deploying to production on Fridays, there’s definitely a deeper problem. Who is even letting you do Friday deploys when they’re so widely acknowledged to be a bad idea? Or who is pressuring you to do them when you know they’re a bad idea? And why is that person getting away with it? Who is pressuring them? Why is it more important to say something went out than to be sure that somebody is around to fix it if anything breaks? Or, and this is really scary, is management assuming that developers can be called in any time night or day to fix things?
Unfortunately, there isn’t a neat set of fixes for process smells the way there is for code smells. If I could give you a few quick and easy steps to convincing your company that deploying to production on a Friday is a bad idea, I’d be a millionaire :) What I can tell you is that you need to understand why your team lead / manager / upper management thinks it’s a good idea to deploy anything on a Friday before you can convince them to change. If the problem is that upper management believes it’s lazy to wait until Monday when the deploy could be done on Friday, then explaining that deploying on Fridays hurts team morale by making people worry their weekend plans will be wrecked by a surprise issue isn’t going to convince them anything needs to change. On the other hand, if you remind them how angry your customers were the last time production went down on a weekend and stayed down for hours because that particular release wasn’t possible to quickly roll back and the developer who knew the most about that change was away on a long-awaited hiking trip, and tell them there’s a way to prevent that from happening again that costs zero dollars, you’ve got a much stronger case.
This particular question is about learning a new codebase at work, but I think there’s plenty of stuff here that would carryover to learning the codebase of an opensource project you want to contribute to.
When you want to learn a new codebase, the very first thing you need to do is get it to build on your machine. Deliberately breaking things is an extremely useful tool when you want to understand a codebase, if you can’t even build the app you have no chance of figuring out if you broke it or not.
At work there should be another dev around who can sit down with you and help you get everything set up so you can compile, if you’re working on an opensource project you’re going to have to rely on docs, maybe a community chat like slack, gitter, etc, and sheer stubbornness.
Next you need to be able to run it or at least run the tests (if there are tests). This can be a lot more work than just getting the app to compile. For example, the app I work on at my own job compiles really easily but it won’t run unless you have all the environment variables set up correctly and the database and cache and everything installed. You’re probably going to want that coworker’s help here too. If you’re lucky your company has docs you can read and if you’re very lucky they’re even up to date, but at most places I’ve worked the development environment setup process was never written down, only passed down orally.
Quick aside: I’ve said this before, but if your company doesn’t have any docs, write them! Seriously, you’ll make things so much easier for the next dev, who might even be you. If your computer dies on you and you need to get setup again in a hurry, you’re going to be really grateful if you can follow the docs instead of desperately trying to remember how to do that one tricky bit.
Depending on how your app works you may also need help creating an account for yourself so you can actually log in and use it. Once you can build, run, and log in (if you need to), you can really start digging into the code.
What makes the most sense to me is starting from the UI and working my way in from there. It also really helps me to have a goal, like a bug to fix or a small feature to add. I get lost if I just sort of wander around a codebase trying to figure out how everything fits together, having a goal helps me focus.
It’s also totally okay to ask another dev for a quick overview of the app, having a bit of context can make it a lot easier to figure out how everything fits together. Customer documentation can be really helpful here. Sure, it won’t tell you which controller handles the data for the user details page, but it will tell you how to get there and what you can do there.
Eventually you’ve got to get in there and start looking around, though. I poke around the UI until I find a screen that looks like it could be related to the thing I want to do, then I copy some text from that screen (ideally something that looks distinctive) and search for it in the code. Most of the time this is helpful but sometimes the text you thought was distinctive really isn’t, you may have to search for a few different things before you find something that gets you useful results.
If the app is a webapp it often helps to open up the network tab in your browser dev tools and look at the calls the frontend makes to the server. If you’re not sure exactly which endpoint is actually getting called this can clear things right up. Don’t be afraid to add more logging either, just try to remember to clean it up when you’re done :) I do that all the time when I’m trying to figure out exactly what my app is up to. If you want to get fancy you may even be able to attach a debugger to your running application and step through the code one statement at a time. I have this irrational dislike of doing that (I find stepping through the code tedious and would rather just throw in another System.out.println), but if it works for you, great!
Once you find the right server calls it should be fairly simple to trace your way back through the code all the way to the database (or other data store) if you need to. I mostly work in Eclipse, so I’m working from the assumption I have good IDE support and can easily navigate the project once I decide where to start. If you aren’t using an IDE that makes it really easy to find the definition of a class or method, you’re just going to have to do a lot of searching.
IDEs can only do so much for you, though. When I’m trying to figure out a complicated part of the code I’ll sort of draw myself a map so I don’t get lost. That is, I’ll write down, either on paper or in a text file, exactly what calls the method I’m looking at, and what calls those methods, and what calls those methods until I’m clear on how everything fits together and where exactly that one bad parameter came from.
Like I said earlier, breaking stuff on purpose can be a great way to verify that you understand how the pieces fit together. Once you think you know where the problem is / where you should add your new feature, try commenting out the body of a method and seeing if things break the way you expect. If they do, great! If they don’t, hey at least you found out sooner rather than later that you were wrong about how it works. Another good way to break things is to give them bad data. I do that when I want to know if my request is even making it to the part of the code I’m interested in – if it doesn’t break the way I expected then I know I’m wrong about what the code is actually doing.
If one screen or endpoint or whatever just doesn’t make any sense to you, try another one! You may just need to see more of the app before things start coming together, or you may have just had the bad luck to stumble across an especially tricky part of the app. Keep trying, and ask for help if you need it, you’ll get it.
Speaking of asking for help: most developers hate doing it, but it really is okay to ask for help if you get stuck. Especially if you’re a junior, it takes practice to get good at finding your way around new codebases. Five minutes of explanation by someone who knows the code could save you hours of confusion and frustration. The faster you get up to speed on that new codebase, the more help you’ll be to your coworkers, so it’s actually in their best interests to interrupt them (after trying to figure it out on your own, of course) to ask for help.
One last tip: it takes months, literally multiple months, to really be productive at a new development job. If you feel stupid and useless, that’s a) not true, and b) totally normal. You would never tell a new hire that they’re an idiot for not instantly figuring out a new codebase, so try to be that nice to yourself, okay?
Why tomcat? Because it’s open source, I’ve used it before, and honestly it was the first thing that came to mind :) Oh, and for anyone reading this who hasn’t spent a ton of time writing Java webapps, a servlet container is a kind of webserver that provides a nice little home for your servlets, which are little Java programs that take http requests and give back http responses.
Tomcat does indeed have a main method, it starts on line 457 in Bootstrap.java. There’s even a helpful comment that says “Main method and entry point when starting Tomcat via the provided scripts.”
Which is all well and good, but that makes for a pretty boring blog post :) So let’s talk about how I hunted down that main method. I could have just searched for “static void main” in the Tomcat github repo, but a) I just tried that and it wasn’t actually super helpful, and b) what I really wanted to know was what happens when you start Tomcat.
It’s been a while since I used Tomcat so I looked at the docs first. In the readme on the github project there’s a link to RUNNING.txt, which looks promising.
So I skim through that (there’s a bunch of stuff about installing tomcat and setting up environment variables that I don’t care about right now, I just want to know what the startup script is called) and eventually find the section about starting tomcat. In there it says that the startup script for linux users like me is $CATALINA_HOME/bin/startup.sh (there’s a .bat file for windows users too if you’re curious).
Okay great what’s in startup.sh? (also thankyou github for having a file finder!) It turns out there’s not a ton in there and I don’t understand most of it, but at the end of that file is “exec“$PRGDIR“/“$EXECUTABLE“ start “$@“”, which I think I can assume executes a thing :) Also a little earlier in the file is EXECUTABLE=catalina.sh.
Given that, it looks like startup.sh does some pre-setup setup and then catalina.sh actually starts the server (if there’s a 3rd level of startup scripts I’m going to be really sad). There’s a lot more going on in catalina.sh that I skimmed through, but I eventually started seeing a lot of evalexec“\”$_RUNJAVA\”“ [many more vars] org.apache.catalina.startup.Bootstrap “$@“ start. That $_RUNJAVA thing sounds like it might, you know, run Java :) That probably means that the startup class is org.apache.catalina.startup.Bootstrap, let’s go see if there’s anything interesting in there. And what do you know there’s a main method. It took some scrolling to get there but we made it eventually!
You may or may not care about how Tomcat (or any other given application) works, but what I hope is interesting is that everything an application does is understandable. Nothing the computer does is magic, it’s all knowable.
Yes, but not the way the questioner seems to think. The bad company in this case is very disorganized, which I’m sad to say isn’t exactly unheard of. That company does sound like an especially bad example, but if you get a few developers together you’ll hear plenty of very similar horror stories. However, I don’t think a company like that will necessarily hurt your career, especially if you’re a junior like the questioner. No reasonable human being would blame a junior dev for not being able to completely change the culture of a company.
However, I do believe there are other kinds of bad companies that can hurt your career: companies with extremely low standards, and companies with such extreme culture problems that only terrible people succeed.
Companies with extremely low standards can really hurt your career if you stay there too long. If you stick around too long at a company or other organization that has a reputation for low standards, then people start wondering if you haven’t left because you can’t do better. The longer you stay there, the more it looks like you know you can’t get a better job and the harder it becomes to find another job. It doesn’t even have to be true that your company has low standards – if it used to be true or if it’s that one department giving the place a bad name or if people just believe it’s true for whatever reason, your resume is still going in the nope pile whether that’s actually justified or not. You don’t need to panic immediately if your new job turned out to be easier than you were expecting, but I strongly recommend leaving before too long. Of course, it can also make you look unreliable if you have a lot of very short stints at different jobs on your resume, but that’s a separate blog post :)
Companies where terrible people succeed (I’m looking at you, Uber) make people wonder if you’re terrible too. That’s very bad for your career in a field where you basically have to change jobs to get a meaningful raise or title bump. To be fair, Uber is a large company and it would be surprising if every last manager/executive/team lead/other influential person was a complete trashfire of a human being, but if I interviewed a dev from a questionable company I would have a lot of pointed questions. In particular, I would want to know how well they did in terms of promotions and other recognition, and what they think makes someone a good developer and a good team member. Getting regular promotions at a terrible company might be innocent, it could mean you worked with one of the few decent managers, but it probably means that you got along well with a terrible manager. You know who gets along well with terrible people? Other terrible people. I’d also be very worried that someone from a terrible company thinks it’s okay to be a total jerk as long as you write a lot of code. Not only is that terrible for team productivity, but it’s also just wrong to make your teammates spend eight hours a day with a jerk.
On the upside, as long as you don’t work for a company that’s known for never firing anyone or for being gross and awful, your career is going to be fine. Over the long term a disorganized company can do some damage – it’s not going to look good if you end up with five years of development experience and no idea how to use version control, for example – but that takes quite a while and you can mitigate it with side projects.
In short, it’s very hard for any one company to wreck your career. Ten years from now nobody is going to care who you worked for in 2017.
Today’s code smell is temporary fields. Like with all code smells, sometimes these aren’t a problem at all. For example, it’s pretty normal to have a user object with a bunch of optional fields. Sometimes you have users who just start out with a username and a password and fill in their profiles later and sometimes you have fields for stuff like social media accounts that only some users will ever fill in, but sometimes fields that only sometimes have a value are a problem.
One of the ways people try to fix the problem of a long parameter list is to make some of those parameters into fields and then set them later if they need to. Unfortunately that’s not really any better because it just hides some of the confusion under the rug instead of actually getting rid of it. Instead of it at least being obvious you have too many parameters, now you have a bunch of fields that may or may not get used depending on what the object is up to and you have to go hunting to figure out what’s going on.
Since professional programmers read code much more often than we write it, it’s a huge waste of time to have to go on an exploratory mission every time you need to understand that code again. If you do end up needing to update that code, best of luck figuring out which fields are important and whether your refactor / bug fix / new feature broke anything.
I had a heck of a time finding a code example for this code smell but fortunately so did Mark Seemann, who was frustrated enough by that to write not just an example of the smell but of how to refactor it too.
If you find this smell in your code, what do you do about it?
Pulling out the temporary bits into their own object and just using that is a good option (if that works for your code, of course). Isolating the fiddly bit makes the rest of the object cleaner and makes it clearer what the fiddly bit is up to. You can also add a null object (a special object where you put whatever defaults you use when one of your temporary fields is null). That way your normal processing code can be clean and simple instead of littered with null checks. Sometimes that just makes things more complicated, so it’s a bit of a judgement call.
If you’re having trouble figuring out what an object even does because most of its fields are null most of the time, see if some refactoring helps. You can always put it back the way it was if it’s worse afterwards :)
First of all, software development is a team sport. Sure, side projects and prototypes can be built by individuals, but the vast, vast majority of business software is built by teams, which means that communication is everything.
Are you going to go out of your way to communicate with the arrogant jerk on your team? No you are not. Nobody does that. Even if you really need to you’re going to put it off as long as possible. Yes that’s not ideal, and you can make the argument that it’s illogical, but let’s be real, humans have emotions and we don’t like dealing with jerks. To have good communication on your team you have to make it as painless as possible.
Arrogance is also a literal blocker to ideas. Arrogant jerks make people scared to speak up about ideas they aren’t completely sure about because they don’t want to make themselves targets. The bigger the jerk the more scared people get until nobody speaks up at all. Jerks also make people scared to try things – if a jerk is looking for any excuse to shit on you of course you’re not going to try anything that could possibly fail. With a bad enough jerk, nobody on the team grows as a programmer because they never try anything they haven’t done before.
Jerks also make your team avoid code reviews, at least if the jerk is involved. Nobody wants to hear that everything they built is terrible and the jerk could do it 1000 times better even if there are a couple of valid needles in the haystack of outright meanness. You can be sure no one will volunteer to review the jerk’s code either, which means standards start slipping, bugs may get into prod – not because code reviews are specifically about finding bugs but because code reviews are great for catching “this is really complicated and I’m having trouble following it, can you pull it apart and simplify it?” and that prevents bugs – and if somebody needs to change the jerk’s code nobody will know where to start because nobody reviewed their code.
And a jerk’s code often really needs reviewing. In my (thankfully limited) experience, jerks who think they’re smarter than everyone else write overly complicated code that no one else can follow to prove how extraordinarily clever they are. This has been known to backfire when it turns out that not even the oh-so-clever jerk can follow that code. Even if anyone is willing to review a jerk’s code, it’s not likely to improve that code because jerks rarely take feedback well. If they did, they’d stop being jerks!
Yet another way jerks harm your product is by ramming their ideas though by making every disagreement or discussion a huge fight. Eventually the rest of the team will get tired of having that fight over and over and let the jerk have their way, if only so they can stop talking about it. Just like with avoiding communicating with a jerk, you can argue that it’s short-sighted to prioritize avoiding a fight right now over dealing with the long term fallout of bad decisions, but you’ll get farther by expecting people to act like people than you will by expecting them to act like emotionless robots.
Finally, why should any non-jerk on your team bother to do their best when you let a jerk get away with writing bad code, refusing to listen to anyone else’s ideas, and making everyone around them feel terrible? If you won’t avoid hiring a jerk for any other reason, do it because all of your devs who can get a better job will do that just so they don’t have to spend all day at work with a jerk. Life is too short to work with jerks!
So last week I talked about how you can’t know if code is good or bad without knowing the context, what you’re actually trying to accomplish with that code, what was most important when it was written (priorities change, that doesn’t mean that the way the code was written at the time wasn’t perfectly reasonable for the context it was written in).
But there’s more to context than that! You also can’t know whether your code is any good in isolation. In other words, unit tests are not enough and can never tell you whether your code actually works. No seriously, they can’t. That’s because your code doesn’t work unless the whole app works, and the only way you can tell whether the whole app works is with end-to-end testing.
It’s great if all the pieces work according to your unit tests, I’m not knocking those, but they can’t tell you the whole story. Only integration tests can do that.
If you haven’t run into integration issues yet, just wait, it’ll happen :) Maybe your controller doesn’t validate user input quite the same way as the service that handles business logic does. Maybe one layer tolerates nulls and another one flips out and throws NPEs. Maybe the front-end and the back-end disagree just slightly on the format of the JSON that gets passed back and forth. Maybe one layer doesn’t return the exact error codes another layer/object/service was expecting.
Hell, maybe everything is great except for some missing assets on the frontend. That’s not covered by an integration test but it’s not really unit test territory either. Some stuff just has to be looked at by a human. Yes, it’s possible to automate frontend testing but if your UI changes very often at all then it may not be worth the trouble of reworking your tests every time it changes. Plus computers are kind of terrible at stuff like “this text doesn’t line up nicely with this text box,” so you’re going to need a human to look at your UI anyway.
It would be great if unit tests were all you need, and if you have really comprehensive unit tests you will catch most of your problems, but in the real world things break not just within components but between them. An application isn’t just a collection of models, views, and controllers (assuming you went with a MVC architecture), it’s also the interactions between those components. If you aren’t testing those interactions, you just aren’t testing enough.
Of course, even integration tests have their limits. I once broke production by changing a part of the system that I thought was unrelated to the part that broke. It was not :( (also cache invalidation is the worst). Integration tests are great for things you know are related, but sometimes you just need a human to click around in the app and notice that when you change setting A, feature B that seems unrelated doesn’t see the update.
If you possibly can, learn from my mistakes: it doesn’t work unless it all works together.
That one didn’t go at all the way I thought from the title. It turns out the business priorities actually did make sense (the business had this wild idea that queries, even especially complicated ones, should take less than 4 hours to run) and the programmer was being unreasonably rigid in following “best practices”.
But weirdly, one of the answers said that both parties were wrong – that the programmer shouldn’t have been so attached to having nice looking code and the manager shouldn’t have insisted on using an anti-pattern. What? The anti-pattern (using raw SQL instead of the ORM) improved performance by multiple orders of magnitude! If performance matters at all, you cannot possibly tell me that massively improving performance was the wrong thing to do.
Okay, there is some nuance there :) If your ORM performs that badly it’s likely not configured right for your situation, so it might be better in the long term to fix your config than to keep writing custom SQL for every query that’s the least bit complicated. On the other hand, if you don’t have an expert handy who can tune your ORM config, it might be easier to ditch it, at least for the more complicated queries, and just write some SQL. Or hey, how about a compromise? Use a stored procedure, that’s what they’re for! If it’s a query you run a lot, it might even make sense to make a view for it.
No matter what you end up doing, it’s still not wrong to use a supposed “anti-pattern” if you have a good reason to do it. It’s the context that makes something a bad idea, even the best idea can turn bad if you use it the wrong way. The only way to know whether your “anti-pattern” is actually a bad idea for your particular situation is…. to look at your particular situation! You can’t just say something is an anti-pattern without knowing what the person using that anti-pattern is trying to accomplish.
Let’s look at another datebasey example. It’s generally considered a bad idea to denormalize your database, every database design course will tell you so. Normalizing your database ensures all of your data is always right by isolating everything that can change independently. Since you don’t usually bother to keep data at all if you don’t care if it’s all accurate, you almost always want your data nicely normalized.
But sometimes correctness isn’t the absolute most important thing. Let’s take browser or mobile games, for instance. If your game takes too long to load, your players will just go do something else and they might not ever come back. It’s much more important to be fast than it is to be perfect if you want to make money on a game like that. And if something does go wrong and a player gets mad because their data didn’t get saved correctly, you can just give them game currency until they’re happy again.
In a situation where performance is more important than correctness, normalization is the anti-pattern. Like I said, it’s all about context!
To be clear, there are some things that are just a terrible idea no matter what you’re doing. I don’t care how clever you think you are, your variable names need to make sense. And no matter how well it performs, if your system is so confusing than no one can make updates, then it’s a bad system. But for the most part whether or not something is an anti-pattern is about what you’re trying to accomplish more than it’s about the “anti-pattern” itself.
Everybody likes code that makes sense to them, and everybody likes to feel like they’re doing things the right way, but rigidly following “best practices” just isn’t good enough. If it doesn’t help you achieve your greater goals for your application (ie not just gold-plating your code, but providing value to your users or customers), it’s just not a good idea no matter how many experts call it a best practice.
And yes, that means you can’t know if your code is good or bad without knowing exactly what you’re trying to do. Welcome to professional programming!