One company I worked at had "we hire the best!" as its slogan, and they used it to justify so much bad practice. No code reviews (we just don't hire people who write bad code), no unit testing (same reason), python used for huge sprawling 10+ year old codebases that's on its third or fourth generation of maintainers, and no documentation anywhere. Everything is a spelunking expedition, and there's no type safety or anything to help you figure it out. There are more than a few thousand-plus line functions that make PyCharm chug every time your touch a character.
On the flip side, the Java and C++ codebases were similarly abused (no tests, no docs, four generations of maintainers/oral tradition, no code reviews ever) but are at least two orders of magnitude easier to traverse because the type system keeps everyone honest across decades in a way that dynamic languages just do not.
I'm writing some Java at the moment for an Android app, and it's just killing me after doing Scheme and Python, but I have to admit, if that start-up had used Java or C#, they might have been better off in the long run.
Hiring really good, disciplined people is super, super hard. My current thought is that if you don't have a super-star CTO who can attract and keep real A+ coders, don't use advanced languages. And if you do have that CTO, do it, but make sure you are architecting to make the rewrite in C# or whatever doable when you sell your company. If I were starting my own business now, I'd use Clojure or Scala or F#, and design things so that in 5 years new owners had a decent path to switch to C# or Java wherever they want.
Android isn't Java though. It's some frankenstein contraption made from a mutated version of Java from a decade ago.
> I were starting my own business now, I'd use Clojure or Scala.
I personally wouldn't ever choose Scala for anything. It's tooling is horrible (Sbt is a special hell, and scalac is as slow as a C++ compiler).
I'm currently in a code base like you describe, scala core with a switch to Java. It's not fun. Were stuck with these legacy library and framework choices that don't fit into the Java ecosystem and it slows down development considerably.
Scalac is as fast as Javac if you don't overuse Scala-specific features which are known to compile slowly - implicits and macros. But it is unfair to use these features and than complain on compile speed or compare it to a language that doesn't offer similar feature set.
Also, in our Java/Scala project it turned out that Scala+Bloop was way faster (10x-100x) at incremental compilation than Java+Gradle. Bloop is based on SBT libraries so there are some things that SBT does right.
Kotlin has wayyy better interoperability with Java
i wonder if Java is judiciously choosing the non crazily digressive parts of Scala, as they both evolve.
So far I’ve just seen a complex and wild stream API that mimics it in practice, without all the mental model behind it.
Oh, and what’s this nonsense with Java Option?! Really?
For better I'd say you should look at things like virtual threads vs async/await or kotlins when vs switch expressions.
The question is: if this support is enough or should the type system be enhanced so you can abstract over/compose a functor and/or a monad ?
The answer to this question is IMO still open.
Screw that... I should have been told sooner
"Over the last 12 months, 75 per cent of the most active “buyers” of $100m+ technology companies have been PE firms. This is despite the fact that the combined cash balances of the “Big Five” tech companies is an eye-watering $330 billion. Today’s most active tech buyers are private equity firms like Silver Lake, Francisco Partners, and Vista Equity. Not Google, Facebook or Amazon."
https://ftalphaville.ft.com/2019/01/07/1546862666000/Guest-p...
One of the things that is not obvious to people coming from the VC world is that once the company has gone from VC to PE, it's likely going to get flipped several more times from PE to PE. At a guess, I'd say 3/4 of what I look at are one PE to another. Depending on the firm, they will sell every 3 to 7 years or so.
iain
You used it twice, but it's not on Google...
that’s a logical fallacy. chances are that if they used Java or C# they might be dead by now (ie it sucks but usually it’s a good problem to have)
For any significant Python codebase, I make sure to put extra effort into precise and strict type annotations so I can rely on type safety. Sometimes after fixing all the statically detected type problems, my program works on the first try.
Python was strongly typed from the beginning, before it was statically typed. When you add a string to a number in Python, you get a type error. Python's static type system– which is formally part of the language and has multiple implementations– is an elegant and natural fit to the language. Unlike TypeScript, where the type system is artificial, and restricts what you can do with JavaScript.
Language features that made it into ruby– like blocks and unless– were rejected from Python because they would have made it possible to create advanced DSLs, which is something the language maintainers aim to avoid– enabling the creation of arcane application-specific special languages.
PEP-484 is the static type system's name: https://www.python.org/dev/peps/pep-0484/ (implementations include mypy and Pyre)
I don't feel confident when I program in Python, nor in Javascript exactly for the lack of proper typing. One could argue that if you know exactly what you are doing, types can even be ignored, which is kind of true - but they're there to help the developer and his team. I don't see any good reason to use Python over Golang, for example, if we only consider the language itself and the confidence that you get from using a statically typed language.
So my own heuristic has become:
shell script when < 100 lines of script
python code when < 1000 lines of code
else statically typed, compiled languageHowever, I have to acknowledge that modern IDEs (and even advanced code editors like VSCode) have pretty good support for dynamic scripting languages these days that make working with them in larger code bases not entirely painful.
These were the go-to "prototyping" languages people in science, R&D, etc. used (and still use!), and then you'd port those into more manageable languages for larger scale projects, or actual software products.
These days, Python does all that. Unless you need some extreme performance for your final product, a well-optimized Python program will work just fine.
But still, there's some of that matlab legacy left - instead of one comprehensive application, you have tons of smaller programs floating around, for one-off/single-issue uses.
I've found that using Python for large projects is both fine and productive as long as you utilize type annotations and modules. Ambiguity goes out the window when you do that.
In the end I feel, if you really want that, use a language that treats types as a first class citizen. There are languages with most of Python's attractive features but actual optional typing (we use Groovy), or static with really good inferred types (eg: Kotlin) etc.
Besides VS Code, these days lightweight editors like Kate, Sublime Text, Vim, etc can all take advantage of type checking LSP servers for Python.
It's quite good.
The real culprits, thinking about it since my post, were side effects and mutability. Python lets you create incredibly difficult to trace chains of side effects and mutations, and has basically no decent ways to prevent this. In Scheme, my code is still super small and I can dynamically create all kinds of object like things, but if I want private, I can make it god-damned private. In Python, anything can be changed by anything ("we're all consenting adults") and using side effects in weird ways is actually part of the idiom. I don't know how many times in Python literature I've seen some variant of "you don't need those baroque patterns because we can use 'import' as a singleton, running class initialization as the constructor". And so all the frameworks have crazy thread-local magic happening from bloody import statements!!! Do that too much and you have no idea what's happening where and why, and something as trivial as changing the order of imports can kill your app. Where I was, this had gotten so bad that the app couldn't even be turned on and tested in the normal way, and none of my predecessors had been willing to go through the pain of figuring out what the chain of imports were doing to bugger it up. (Because that didn't look like doing anything productive, I'm sure you all know the drill...)
If I were doing it again, personally, I'd use Clojure and Spec, and worry more about mutability and side-effects than anything else. Just my two cents Canadian.
The best codebases I have touched, in any language ecosystem, either didn't use a framework, or kept the framework firmly isolated from the other code. If you build your code "inside" a framework, it will eventually become an obstacle to change.
Furthermore, the best Java codebases I have seen avoided annotation-driven-development, reflection, classpath scanning, DI frameworks, AOP, etc. Anything that feels at all magical should be handled with great suspicion.
This. True in any language.
I agree with you, asking everyone to be discipline with their code in a dynamic language is a bit too much to ask (unfortunately).
Here I go about the batteries included vs micro thing. People pick Flask because it looks approachable but then DIY features in. The same thing happens with Sinatra+Rails. If the python ecosystem grows and other communities come in, maybe they will bring their culture and standards in.
Take poetry for python. It's basically, "hey ... cargo/yarn/mix/bundler all kind of figured out these ergonomics". Poetry's "why" section in the README really resonated with me. Cross pollination of ideas across tribes is _good_.
But then, I'm biased/blub-paradox of course. And it's definitely in line with the productivity/DSLs/rapid vs types/verbosity/slower modes discussion.
Fun fact, D language forum website is probably one of the fastest and the most responsive websites on the planet [2], and it's written in D but not based on vibe.d or Diamond [3].
[1] https://code.dlang.org/packages/diamond
Not job hunting, but always happy to chat with people doing cool stuff sort-of locally.
I also think rust's ? makes the "return err" pattern much nicer to deal with.
After 30 years of mediocre programming things I hate are unplanned/hidden side effects, train wrecks due to mutable state, and out of control dependencies. Error handling at this point seems like Weltschmerz.
People always ask me 'how do I become a programmer' the first thing I always tell them is 'you need to learn python it is fairly easy to pick up and learn' I aim them at the python org website and their excellent tutorials. Funny enough it weeds out the people who do not really want to be a programmer but thought it sounded cool, as they have to stop and learn something.
Interesting, one of the most successful companies I've seen in my tech due diligence work was doing the same thing in high end scientific computing. They had written their own DSL, and a whole containing application layer in C that could run on any target platform, with GUI adapters for windows, unix, osx, etc. They were doing very well. And they could get amazing scientists to work for them super-productively because the DSL was designed around the scientists needs.
The reason I'm asking is that that run-time flexibility can completely bite you in the ass if someone can submit code that does unacceptable, unanticipated things. For example, I had an engagement with a company to review their main application, that they host. The issue was that while the app was in Java, it accepted user templates in (IIRC) BeanShell. One quick System.exit() in a template as a proof of concept, and Tomcat came thundering down.
The concept still works of course if you restrict the language accepted to some minimal DSL.
Some people may argue, "but documentation would make this an non-issue". Guarantees built-in to the platform itself are more reliable than human aspirations, or automatic memory management as a feature wouldn't have gotten popular.
Consequently, there was a big disincentive to monkeypatching.
I've seen the same occur with Spring based Java. To me this is similar to the old "no one has gotten fired for going IBM". The new phrase would be "no one has gotten fired for going with a static typed language."
When someone writes bad Java code, we tend to blame the developer. When someone writes bad Python code, we blame Python.
If you think it's the lack of types that make things more difficult to follow, you can always force build failure if type hints aren't provided (which Python has supported for a while now) in the same way you can fail your Java builds if test coverage isn't up to par.
Yeap. I ended up dragging my team to Golang due to this. Sure they didn't write unit tests and they still aren't writing unit tests. But at least now a compiler will take a look at the code before it's run. Lots of stupid errors at runtime disappeared.
For personal projects? Something like Python, Lua or Scheme is great. It can still be great in large teams, but they need to understand and care about what they are doing.
> but I would have been happier stepping into C++
Oh no you wouldn't. That's the nuclear foot gun.
I've been tasked(along with some great engineers, most more experienced than myself) to fix a mess left by two other teams. A project that was severely over budget and very late.
It was the worst abomination ever known to man. Crashes in random places. Race conditions. Global variables. Memory corruption galore. Lack of understanding of C++ in general (copy constructors, assignment operators, initializer lists) leading to many more bugs. Leaked like a sieve.
A lot of this wouldn't be possible in other languages (memory corruption in general). Some issues would happen no matter what (two modules opening the same file and writing on it simultaneously), unless something like Rust or Haskell was used to make this more difficult.
So far, that's the only project I've contradicted Joel Spolsky and it was completely thrown away and rewritten. Again in C++ because that was the requirement, but with proper modules this time, smart pointers and the like. It was completed in 3 months.
All of these are trivially detected with msan, tsan, lsan and ubsan.
Rails has a lot of magic that gets you running fast but it doesn't even have a service layer - and instead promotes the abomination that is mixins (concerns), fat models, fat controllers - stuff that gets you running with minimal effort but then crying over the code duplication all over the place and lack of logical separation.
ASP.NET is more verbose out the gate but you're basically guided in to stuff like repository pattern, service layer with POCO models, thin controllers and IoC. Static typing gives you guarantees when reading the code (I've seen RoR concerns that relied on random fields being present in target class, but had conditional logic with implicit assumptions about which class it would be included in - it was a hell to reason about). It's verbose but consistent and built to scale - I'll take a dirty ASP.NET project over a dirty RoR project any day.
This has serious implications for how you develop large systems. Don't try to get too sophisticated on a larger or longer-lasting project. You'll have people on it who won't' be able to play at that level, and if you try to make them, they'll make a mess.
The same thing happens over time. Sure, your team is only 20 people... today. But if the program lives for three decades, how many people will work on it? 100? Will they all be superstars? Even those maintenance people you hire 20 years from now? Probably not.
Actually, I find Nim language [1] to be a great alternative to C++ as far as compiled, statically typed, high performance modern languages are concerned. Moreover, Nim's meta-programming capabilities (generics, templates, macros) are second to none for creating elegant DSLs. And its syntax makes Python developers feel at home. Preempting questions about garbage collection -- Nim allows to switch off GC and manually manage memory if a programmer so desires. Or you can manually manage memory in some critical parts of your program while letting the rest be garbage collected.
In the several years I spent doing .NET consulting, I believe that this is the crux of the problem, and not Python per se.
In .NET land (probably Enterprise Java too), this often manifests as an apparent goal to get every single pattern from PoEAA [1] into every file rather than extensive metaprogramming - but the root cause is the same - juniors not having appropriate guidance.
I don't think so. It is a problem of language.
Or more specifically, of dynamically typed languages (DTL) vs/ statically typed ones (STL).
We know for a fact now that DTL's simply don't scale to large code bases. And before dozens of people tell me there are a lot of large apps written in DTL, this doesn't mean it was a good idea in the first place nor that these large apps are easy to maintain and improve (they're not and they would be much easier to maintain and improve with type annotations).
We don't know that. This is not a fact.
Or they eventually come up with a hybrid micro services.
I recently spent half a day implementing a hacky workaround for something using gc. Once I was happy that I got it working, I immediately laughed at how absurd it was and deleted it.
I still don't think I will ever be happier in C++ but I'm only 11 years into python, and 6 years into coding full time, so who knows :-).
Abuse of untyped ViewBags to pass data to views, using ViewBags to pass things to Helpers, badly configured AOP or XML-based dependency injection, people using Dictionaries/dynamic/JSON instead of objects for passing data between application layers...
As soon as you have escape hatches you get those things that make refactoring a nightmare in large projects and cause new employees to always fail to grasp the full-picture because of how fragile projects were.
In the frontend I feel the same about Vuex. It's a great library, way more ergonomic than Redux and the defaults are great, but the fact that actions and getters are untyped by default in Typescript is a major source of bugs in projects I work on, even in codebases with 100% coverage.
"Since y'all can't seem to use classes and interfaces correctly, let's physically separate the software so changing the service contracts becomes a bigger pain in the ass"?
I'm not even trying to be sarcastic. I'm genuinely curious. I feel like 90% of the benefits of microservices would be accomplished with discipline and good code review, but those things of course are more easily said than done.
(I understand your Facebooks and Netflixes benefit from having completely separate teams responsible for services with more autonomy, but I'm talking about small to midsize orgs that seem to have adopted microservices.)
1. More decoupling means you can use an entirely different langauge
2. Services can be deployed, restarted, etc. independently.
But that comes with disadvantages. The contract then becomes message passing and serialization/deserialization, which is fairly primitive. You can't pass functions/callbacks, and simple static checks become a whole validation problem. It also limits your ability to use interesting types across services.
The XML craze 20 years ago tried to tackle all of these problems (remember XML schemas?), but it became unweildly and didn't really succeed despite a huge enterprise push.
In certain languages, developers may find it difficult to maintain code quality and good design in a codebase of a certain size. So they find other ways of achieving good design by introducing service boundaries.
In fact I never thought I'd say this - for the first two years of discovering sum types, I thought they were the silver bullet of computing, and certain experiences have sadly tempered me there :)
I still agree that types _can_ make the flow and transformation of data in the system clearer than if there were no types. Though without experience, we could very well end up with convoluted designs and that can be as difficult to untangle as a dynamic spaghetti.
Elixir is all about immutable structs and pattern matching. I don't think "hyper dynamic" is a fair characterization at all.
The upside with the python code is that a complete rewrite of parts that has get extended beyond recognition tend to be easier than expected.
That sounds like a disadvantage tbh
I agree on this part. The problem is many teams with 5 Ruby programmers is doing what 100 Java programmers' work. There would usually be many teams in Java projects, and nobody have full picture of the project. Then people would create jobs out of nowhere because they have to do something... Just see how many projects have dedicated teams writing piles of code for Kubernetes.
I would rather hire much fewer elites for higher price instead.
Engineers are perpetually keeping mental tallies and notes in search of data on which tools, methods, and technologies will help them reach the promised land. I think really the majority of that is noise.
I'm using it sometimes, what's the issue?
Yes, I see it all the time. People think that `gc.collect` collects all the garbage, but that's not true. It only checks for reference cycles.
So a hierarchy of choice for me would be like:
S The languages mentioned above
A Languages with Hindley–Milner type system and inference (SML, OCaml)
B Your typical blue collar languages (C, Pascal, Go, Java, C#)
C Scripting dynamic languages (JS, Perl, PHP, Lua)
That said: I wish you the best of luck!
It does suffer from an excessive overloading of operators (“a” times 6 shouldn’t work). But that’s neither here nor there.
If Python wasn’t strongly typed, it wouldn’t be conceptually possible to move it towards explicit typing as the current push is.
Now, it’s not statically typed. You don’t get to assign values and interpretations to memory addresses. This is what “performant” people usually defend. But _that_ has nothing to do with typing assuming semantic roles in codebases.
Here are some references (there might be some overlap):
- https://labs.ig.com/static-typing-promise
- https://danluu.com/empirical-pl/
- https://medium.com/javascript-scene/the-shocking-secret-abou...
- https://www.researchgate.net/publication/259634489_An_empiri... (one of the original studies)
I hope it's clear I'm not implying that there are no advantages in using a statically typed language, only that it's often seen as a solution to a problem that doesn't originate there.
PS: what is up with the downvotes? What is this, Reddit?
Source?
Sure, this is a problem of people, not language. But there is something to the argument that absent discipline and experience, these can be dangerous. One can detangle a dog's breakfast in Java Spring a lot more easily.
(ever seen "import gc" in a Python program? yes, that means what you think it means..)