Thousands of files all become filled with very obvious bugs once you enable a basic type checker, which then helps brand Typescript as "too much effort" by those who normally write Javascript because now they need to deal with their buggy code. Luckily you can just explicitly cast all of your types without checking and hope that nobody notices!
When you ask people to get their typing right, someone will use buzzwords like "rapid prototyping" and remind you that features are more important than fixing the existing product as long as nobody complains about the bugs, and that's that. Also, Typescript is hard, apparently.
Untyped legacy code sucks. It's one of the reasons I detest working with languages like Javascript, Python, and PHP. At least Python and PHP have types these days, making Javascript objectively inferior.
Sure it compiles. But often it crashes without rhyme or reason and for some reason, customers hate it.
First thing I did was to get it from .net 4.8 to .net 7 and enable all available errors checkers and style linters.
The other howled, but their builds stopped because they could no longer check-in crap. That helped. But fixing this is more work than what this would have needed if they had done it right the first time.
Funny examples: reinvanted rabbitmw badly using raw TCP and XML. Better hope your network is good, If the messages arrive at the same time, they are merged and the app crashes. So there are lots of time.sleep(50) calls to avoid that. Why is that app slow?
The iot devices can only address 254 states. The UI for that allows ulong devices. It's never checked, but if the vast fails, the app just crashes.
This might not have been a great idea, I'm fairly surprised nobody stopped you before you went through with it.
4.8 is the "final" version of .NET Framework. It's pretty much the longest "Long Term Support" offer Microsoft will ever give for a platform, realistically they'll be supporting it into 2030 and beyond. I'd not start any new projects in it, but upgrading away from it probably wouldn't be the first thing I'd do on a new project.
From .NET5, the framework moved to fundamentally different approach. It's effectively a different framework with the same NET branding on it. Most organisations who decided to move from 4.X to 6.X went through a near total rewrite because the paradigm shift is so massive, lots of organisations decided to stick with the pre-5 versions. The architecture and technology choices probably make a lot more sense in the pre-5 version of .NET, and it's unsurprising that your colleagues are frustrated with this decision, irrespective of the quality of their code.
On top of the move between .NET paradigms, you've moved away from a long-term-support version. NET7 is an 18month short-term-support interim release between two Long Term Support 36month versions .NET6 and .NET8. By moving to 7, you've trapped the company into an early & rapid upgrade path to .NET8 as soon as it comes out in Nov 2023, rather than giving the full 12 month buffer you'd have got if you moved to .NET6, which is in Long Term Support until Nov 2024. You moved the application off of a rock-solid framework with near infinite long-term-support, onto a framework that'll be deprecated before in four months, and out of support entirely in ten.
If I were you, I'd probably not be bringing this decision up in any performance reviews or interviews at new companies.
We moved from 4.8 to 7 because a) the better compiler warnings helped to reduce the amount of shit people where able to check-in AND highlight problems in the existing codebase (beyond "well, it looks like written by the lowest bidder offshore guy drunken on Friday night") and provide management with metrics they could understand (two warnings per LOC is part of your problem). B) it deepened our hiring pool, because it's easier to find people willing to work with current technologies than legacy. C) it allowed us to pull in various modern libraries (BLe especially) which made whole swathes of swamp code irrelevant. D) allowed us to establish a sane Ci/CD Pipeline more easily which we needed to reject bad stuff. F) we ship this product every few months, so not being on lts is not really a problem.
But this crowd believed it obviously. The chief perpetrators already left when I got there, so I could not ask directly. Zero tests, zero documentation and a coding style like somebody tried to write pure C in c#.
In actuality, writing software that is some quality is so hard that one needs all help one can get. One absolutely needs type checking and is just not professional without. One absolutely needs automated tests and is just not professional without. One absolutely needs manual/exploratory testing and is just not professional without.
If culture is that tests are necessary, then these people hide and write their tests (or actually learn that test-writing makes sense).
I am in an organization currently that values testing overall, but being in a regulated industry its almost a little over-done, the result of that is, that for prototypes, tools, etc. the rules are weakened, leading to this weird situation where "of course we write tests, but this was just our prototype, that runs in production, for 3 years" seems to work in some departments...
Just because something works, doesn't mean we can't improve it, and just because we can deliver value with one tool, doesn't mean that we couldn't be delivering more value with other tools.
Granted, this has the potential of the opposite problem, convincing detractors that static analysis didn’t find much, but that can be avoided by keeping a list of the bugs that were resolved ahead of time.
For people who know nothing but JavaScript and dynamic typing, it could be.
Just because something is 'legacy' doesn't mean it's crap. It cauld be well thought out and easily maintained.
I've been dealing with a straightforward legacy api in JS. Trying to port it to typescript, the hard part is typing classes with dynamic propertied based on a schema. Has worked great in production for years. Well tested. I have to rewrite it to make it workin typescript apparently. going from about 10 line files to 50+ just for typings. Im sure i'm doing something wrong, but i've asked and it seems my code is wrong? despite having worked great for me all this time.... Yes i'm salty
That said, some people see TypeScript as "Javascript, but with types in the definitions", but it's more than that. Sure, the core of it is that you're adding types to your code, but with things like KeyOf<>, Partial<>, Pick<> and other such utilities the TypeScript system is a lot more powerful than that of many statically typed languages.
Typing dynamic JS with TS can be tough because complex dynamic typing requires a lot of assumptions that often are half-truths from a programming language point of view. "When x.foo is a Number then x.bar.baz is a String with at most two characters" can be expressed in Typescript but it's very verbose and requires some digging through the Typescript docs to get right.
Describing types does add more lines of code, but that's not necessarily a bad thing. What you're doing is taking the implicit assumptions based on your dynamically typed program and expressing them in a way that can be verified. Assuming you don't invent completely new types in every single function, the initial typing workload is high but it becomes a lot easier over time, especially if you use your Partial/Pick/keyofs well.
What I found to be helpful is that the ?. operator has been added to Javascript quite some time ago (legacy JS doesn't normally use it) which means you can define properties that don't exist in the beginning but are set down the line to be of type `foo: string | undefined` rather than invent implementations of interfaces for every modification step of the program, using it through `bar.foo?.baz` rather than `bar.foo.baz`, and maintain the exact same assertions your program had before.
Alternatively, using `!` when you're absolutely 100% sure some parameter has been filled in (`bar.foo!.baz!`) can help convince the transpiler of assumptions that can't easily be expressed in code.
It makes writing the happy path slower, but is writing the happy path _programming?_
By the time you have a program which is complete and (at least mostly) bug free, the static typing has saved you hundreds of hours.
> It makes writing the happy path slower, but is writing the happy path _programming?_
Most managers and even some programmers seem to think so. Build the thing with no quality control, ship it, deal with the fallout, repeat. Bad programmers think this is normal, good programmers ensure that any fallout is rare.He's not gonna be the one using the sword and he can make it clear up front about the tradeoffs. If it shatters in use and the customer gets maimed, oh well, they were warned. Sometimes the market is flooded with people who just want cheap swords made fast and the blacksmith needs to pay the bills too right?
In effect, https://en.wikipedia.org/wiki/The_Market_for_Lemons
I would say if their society can grant them the safety to turn down work for cheap swords at their discretion without the risk of having their life ruined through a down period that is lacking high-quality work orders, then maybe you might have a case for also holding the blacksmiths accountable to a higher standard of work.
Accepting and taking on the risks associated with highly selective work orders has it's own intrinsic value and should be rewarded somehow. If it's not, then why would anyone do it?
--
It seems that this comment is controversial and I'm not sure why. I'm referring to the ability to do something like
h <=< g <=< f
in Haskell, compared to a much longer explicit error checking equivalent in Go etc.It's far more common to see this chaining done with `do` blocks but I wanted to keep it all on one line.
This lends itself to a clean and optimistic sort of programming. You just write what you want to happen, chained within the broader context of some exception that might occur. So I agree with the parent that static typing makes things faster and easier, but I would argue that most unhappy paths don't require unique attention, and that it's language smell to make you care about them.
The happy path is just fine for many cases.
Your home isn’t built to withstand a determined attacker with a tank. But we live in a happy world and a lot of stuff exists because people can cut corners.
Good managers/programmers know where they can be sloppy and where they have to be super careful.
The beauty of technical debt is that it only has to be repaid in case of success.
I’m being generous tbh. In my experience, though I work on pretty critical software, the unhappy path is a fairly critical path and must have some intentional barriers built before being shipped to customers.
Maybe that's the difference between programming and software engineering! Engineers care if their stuff breaks.
-- Hammurabi's Code, Babylon 1755–1750 BC
Modern construction law isn't as brutal, still liabilities will be questioned if corners were cut, and brought to court.
In many cases, programming is still at the level of driving chariots with steam engines.
No sane person says that. Also, static typing is not a thing (or at least, what you call "static typing" doesn't mean anything).
Type checking can be static. And in this context it means that it happens before the program is run, so it doesn't affect the speed of the program.
Your conjectures about how static typing (but probably actually checking) saves time developing is also nonsense. Static checking is helpful for analyzing programs, of course, but isn't the primary driver behind program quality, not even the second most important one. But, most importantly, quality typically only adversely affects the time to market. Working towards more correct programs will make the whole process slower and more expensive. Perhaps, at some very bottom of the quality pyramid you can find examples of the opposite -- when the program is so bad that it actually negatively impacts time to market and that metric can be improved by marginal effort in quality control... but if you are out of the swamp, this doesn't work anymore, and any increase in quality will negatively impact ttm, cost, number of employees needed etc.
Might seem like nitpicking, but the 'probability of a bug being triggered in the future' is an important metric for figuring out if large scale refactorings are actually worth the risk of introducing new bugs.
Disclaimer: I'm coming from the static typing hemisphere, but adding type information to a codebase that was originally written with runtime-duck-typing in mind sounds like it might actually make maintainability of the code worse - that's at least my experience after trying to do exactly this in a medium-sized Python codebase - some of the 'typings' ended up so complex and weird that they completely dominated the code and turned what was very simple code at the beginning into an unreadable mess (looking almost like C++ template code). I eventually rolled back the changes. Basically, if I wanted a statically typed language, I wouldn't have chosen Python in the first place for this type of project.
If I were starting a project today in Python, I would definitely try setting up my tooling and processes to enforce type checking to at least some extent. Fortunately, we have a lot of guardrails that recover from the typical exceptions that occur in those cases, but as I experienced first hand, it's very easy to introduce new bugs when relying only on my intuition for those difficult-to-spot cases. Tooling definitely helps there.
My previous role had me writing TypeScript and Rust so there's always the temptation, or bias, to advocate for strict typing everywhere, but I'm conscious that it's neither practical nor feasible in large codebases with hundreds of contributors (most of whom may be more accustomed to dynamically typed languages).
They just don't know it, and the people hiring them don't either.
It's not a new phenomenon, we got the same when Java arrived, then PHP, etc.
Every time something makes things easier, you get a wave of beginners set to create things they are not qualified to do. And because you now need software for everything, leaders also get into positions where are not qualify to make some decisions about it. You pair unqualified leaders with unqualified devs, and you get this.
And it's now easier than ever to be in this position, also you are more pressured than ever to get into this position.
I've worked on plenty of big Python systems myself, it's not the tech per se (it has the usual pros and cons game like every language), but Python is the language used "when you don't know programming".
Which is ironic, because when I started to use it in Python 2.4, only passionate devs used it because it was niche, and we had the opposite effect.
How easy it is to call other people not qualified because they have different opinions.
I've seen more projects failed because the only thing the devs cared about was an abstract code correctness, which made even basic PRs getting polished for weeks. I don't think you need to worry too much about types or tests if you are just doing a prototoype to validate ideas or your user base is 0.
To this day, I'm baffled by the dynamic language folks who cannot get their head around how strictness/rigor (via a good expressive type system) actually makes maintenance easier and more importantly: cheaper.
SLOC Directory SLOC-by-Language (Sorted)
232099 tests python=232099
102470 django python=102470
424 docs python=424
158 scripts python=142,sh=16
21 top_dir python=21
0 extras (none)
0 js_tests (none)
Totals grouped by language (dominant language first):
python: 335156 (100.00%)
sh: 16 (0.00%)
Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."Depends on the project of course, e.g. SQLite has an insane ratio.
if very_rare_condition:
raise MyRareException
Java wouldn't compile (no exception instantiation), but Python would work fine in production until the rare condition happened (but not captured above, as it's a class, not instance). Cases like that justify 100% tests coverage in Python, but not Java.A static checker with global type inference could be a middle ground. Pylance/Pyright already does that for most VS Code users. A more advanced form of this could track types that are too tedious for humans, like list emptiness, bounds, string alphabet, functions that can raise exceptions, idempotence, side effects, etc.
Those types can then be shown in the IDE, according to user preference, like docs or source control metadata.
I have a feeling that with enough caching this could be done with ok performance, but I hear that useful error reporting is an unsolved problem.
When I advocated for the importance of static typing for software robustness the response I got from many other industry people I knew was that this was the job of intensive unit testing. That the supposed loss of rapid turnaround that came with static types wasn't worth it, and that test-first-design solved this problem.
Now Typescript and Rust are common place, and we have Pythonistas annotating their programs with types. I'm happy, but maybe a bit bitter about the past :-)
You can break almost any python code by passing in the wrong type. The code throwing runtime type-errors IS the type checking.
The danger is only if the code happily operates on the wrong types and returns incorrect results.
I wish more people started using Beartype, it makes Python bearable
Are there any tools for that? If not could one be made? Sounds almost like an advanced linter.
Also, imagine how much more productive you would've been if you instead of doing what you are doing you'd do something worthwhile, like, design the program or organize the testing better.
I've heard so many nonsense claims like yours both in proprietary projects and in open-source ones, and not a single time did the code base became better. In most cases a small(-er) pile of garbage became a bigger pile of garbage.