Yes, C++ is a complex language. Yes, it has a ton of warts if you know where to look. But, you can write beautiful software with it, and it can even look beautiful too.
Yes, there are alternatives, but when it comes to performance, expressiveness and actual deployability, C++ is pretty awesome.
Meh. The performance is almost never worth the huge complexity jump. Instead, profile an app in a higher level language and if any part's too slow, implement that in C/C++. That'll quite often be a low, single digit percentage of the app. Get the performance for substantially less engineering/maintenance cost.
C++ is a complex language no doubt. But software written in C++ need not be complex.
Java, on the other hand, is a simple language. But the kind of unnecessary complexity I have seen in Java-land (EJBs, Spring, etc.) has no parallel in the C++-land.
So going by your argument, I would choose C++ over Java to avoid the complexity jump, then profile the app, and if any part's too slow, improve that again in C++.
The enterprise architects that created those kind of designs, were the same ones that on earlier decade were doing them with C++.
Apparently micro-services are now a thing, well on the late 90's we had Sun RPC, CORBA, DCOM. All tied together with a cluster distributed transaction management.
Sprinkled with code generation tools, based either on UML or Booch diagrams.
It was lovely, then came Java with CORBA support out of the box, RMI and GC. So they moved camps.
And yes, I am also to blame for a few CORBA objects, maybe still runing on a couple of HP-UX systems.
In the C++ community, that OO centric coding style is called the Java style, which is completely unfair, as it really originated in C++; it was just wholeheartedly embraced (and made more usable mostly thanks to GC) by Java.
I suspect (but I am not certain) that any language that wants to work in that space is going to turn into Java EJB or C++ with CORBA and/or DCOM. I think it's the space, not the language, that produces such appalling monstrosities.
(And, if you needed that kind of thing, even EJB or CORBA was better than implementing that same functionality by hand...)
Java just pushes the complexity into the user's code. For example, a friend of mine once gushed to me about Java IDEs - with one click of a button, a hundred lines of boilerplate are automatically added!
I replied a good language shouldn't need boilerplate code automatically inserted.
Boilerplate bureacracy usually happens, when Configurations are not itteratable and/or a language does not enforce the providing of meaningfull defaults. I find both sides guilt and charged here...
>>language does not enforce the providing of meaningfull defaults.
There is more to this. This can can also happen if the language doesn't have features to heavy lift complicated code patterns well. You have to then use massive amount of code wall texts to make the same thing happen.
Part of the reasons why C based languages seem to die all the time, is because you sooner or later have to add features to catch up with the complexity of software getting written around. That either causes enormous amounts of ugly unusable bloat, or you have to go decades of backward compatibility breakage. The languages are just too brittle to work with change.
To give you an example. The best innovation that has come out of Python as a language is they changed the print feature from being a keyword to being a function. This is the biggest innovation they could manage in decades. And even this requires breaking backwards compatibility and having the entire world's Python code bases to go through several decades of upgrade cycles.
You see all this and just move on the next new language, like Go. And then the cycle starts again.
I find Spring to be great. I grant that I've used it since 2.0, but really it does everything quickly, easily, and is well documented. Especially with Spring Boot.
I'm writing a core service in Go because Java 9 broke a Maven plug-in (that I don't need now). Holy crap is it painful. I know that I'm learning, but it's hard to layer the application. Most tutorials show passing the database connection through all of the functions, or use closures that define all of your routes in such a way as to make the db visible.
Even if you get past the difficulty of hiding everything, it's oddly not a good language for web services. The handler interface in mux doesn't allow you to return an error! No error in GO! So you're either going to have a tonne of boilerplate for the routes, or use panic, which by all the reading I've seen is a terrible thing to do. Regardless of which you choice, Go appears to lack the niceties of prebinding the JSON into a struct. Boilerplate. Oh, and you have to do the same to write the JSON out!
Now Java allows for annotations, and types with constructors in the input parameters of the route handler. Spring will inject interface implementations by name or type. All of this and Transactions! It will automatically bind the JSON to the input variable and automatically create the JSON format on the return.
Ever tried to use transactions with Go? You have have to personally keep track of it. Every SQL call has to peg against the transaction because the connection pool won't manage it for you. Also the DB and Transaction structures have the same general interface, but DON'T implement a COMMON INTERFACE! That idea, in 2018, is an experimental feature that MIGHT NOT GET PICKED UP. The maintainers of the standard SQL library said figure it out for yourself.
Really, I don't think that Java brings that much complexity now. It use to. But you can get a new developer up and running in Java within 2 months. It will be better structurally, easily testable, and safer than Go.
Go is like a tricycle. It's simple and it will get you there, but doing anything complex will require a lot of effort. Java is like a 10-speed Schwinn. Fast, moderately complex, but easily understood when kept within the wheelhouse of Spring + Core Language.
I don't use go but some of your more general complaints sound like good things to me.
> Regardless of which you choice, Go appears to lack the niceties of prebinding the JSON into a struct. Boilerplate. Oh, and you have to do the same to write the JSON out!
The more time I spend doing maintenance the more I've learned to love this sort of boilerplate. It makes it much easier to trace where things are being used across the system, no reflection magic that causes the trail to run cold and forces you to use a debugger. I know programmers hate writing boilerplate, but it really isn't so bad and makes maintenance that much easier.
> Ever tried to use transactions with Go? You have have to personally keep track of it. Every SQL call has to peg against the transaction because the connection pool won't manage it for you.
Again I like this explicitness and don't want this hidden. Whether I'm operating inside a transaction scope or not in a given piece of code should not be a mystery.
> Also the DB and Transaction structures have the same general interface, but DON'T implement a COMMON INTERFACE!
I agree that a common interface might be nice, but given that there is seldom a good reason to operate without some kind of transaction is it really that big a deal? Just always use the transaction interface.
YES. 100x yes to this. I started a new job some time ago where Java / Scala were mainly used (prior C++ background).
I can logically step through flow control and figure most things out. Except the amount of "magic" for the sake of reducing boiler plate made debugging some issues really tricky.
Anytime I'm about to reduce boiler plate somehow, I try to ask myself whether it's going to introduce some type of tribal knowledge. That is, will someone without any knowledge of e.g. this codegen be able to step through and understand what's going on.
I feel that people reduce boiler plate without constraints far too often
Also, if you really want to write code that's agnostic to whether you're working directly on the DB object or a transaction, you can just declare that interface yourself; it's not like in java where the implementing class has to opt in.
The absence of the interface being predeclared for you does mean more boilerplate, but I agree that transactional vs. not doesn't strike me as a good thing to be hiding; this particular case seems like maybe a very reasonable omission. I try to avoid abstractions that leak.
>>Really, I don't think that Java brings that much complexity now. It use to.
That's because these days you don't learn Java, you learn things that manage Java madness. Like Eclipse, IntelliJ or Maven etc.
You don't really do much using Java these days. You do things using tools, which write bulk of the Java code. In essence learning Java today is learning Java ecosystem tools.
>>But you can get a new developer up and running in Java within 2 months.
That's because it doesn't much time to learn the IntelliJ UI :)
>>Java is like a 10-speed Schwinn. Fast, moderately complex, but easily understood when kept within the wheelhouse of Spring + Core Language.
This is true for most languages today. Java owes much of its success and power to two things. Libraries and Marketing. Marketing money is gone as Oracle doesn't spend a dime on anything that doesn't bring in two in return. And other language ecosystems have caught up with libraries.
So you could use anything instead of Java and it would all still work, in fact in most shops it already does. Haven't heard any major project being started in Java in most places in a long time.
Legacy projects will carry the Java carcass for a lot of time. But If you want to work on projects worth working, Java isn't the language you should be with right now.
What you do see is people abandoning the "prototype languages". At least twice a year HN has a story where some company dropped Python, Ruby, etc. in favor for Java. They didn't just port to the VM for Jython or JRuby. They re-made the whole thing in Java.
Why not skip this step and just use Java. Yes, there's great tooling that makes Java easy to use. That's the point. You don't have to use JavaBeans for JSON. Expose the properties as public. Jackson's got your back.
Fundamentally, a good design has bounded contexts. The web API should not directly expose your domain objects to the Internet. There should be a mapping between the service boundary to at least the use case boundary. Exposed properties are find for DTOs like JSON bodies. Map those to your domain objects via a translator and you're good. A lot of bulk drops with this approach.
As a language, yes Java is a bulky. But it's wonderfully readable. I know what type a variable is without having to dumpster dive into the method invocation (looking at you Python and Go). I can know directly through a type hierarchy which components implement an interface. In Go you have to explicitly set the supposed implementation to a throwaway variable just to have the compiler tell you if it works. Yes, checked exceptions are a pain. We know that. When it came out, it was a theoretical good. Unlike Go, at least you can wrap a checked exception with a runtime and move on with your code.
To this day Java is the balanced language between performance and usability (along with C#). It is showing its age. It does need some renovation, but on the whole it's a fine language to do new things.
I big part of learning Go, once you've done a lot of Java is to unlearn the things that you had to do as a successful Java programmer. You're used to looking at problems through the lens imposed by the imposing bag of tools and abstractions that is Java, as well as obsolete language design decisions (I'm looking at you, inheritance).
Resolve to look at things and think about things differently.
I have to give credit to Spring for saving Java from itself for a while, but it jumped the shark at some point. I've talked to many devs who've come from teams where only a small number of people understood the magic going on, and they never felt like they could really contribute because they couldn't scale the complexity hurdle of the modern Java environment.
> I know that I'm learning, but it's hard to layer the application. Most tutorials show passing the database connection through all of the functions, or use closures that define all of your routes in such a way as to make the db visible.
Either you are confusing verbosity with complexity or you have never actually written a "complex" software in C++. There are too many things to think about while writing a piece of code in C++, most often than not, you are bound to get things wrong.
I've written and shipped plenty of software with modern C++. Ownership and move semantics mean that after writing a few helper and utility functions, things go pretty smoothly. The most concise and direct software I've written has all been in C++. I can organize and transform data directly instead of jumping through hoops or suffering from enormous amounts of overhead and indirection in a scripting language.
Not to mention I can create stand alone executables that have no dependencies and wind up smaller than most scripting language's interpreters for entire openGL based programs with GUIs.
One thing that doesn't often seem to get considered here is what your user wishes you wrote it in. I don't want my software to come as an assortment of scripts that depend on interpreters that themselves might have dependencies. One file is all it has to be.
I am not confusing verbosity with complexity and I have written all kinds of C++ software over a career of 15 years ranging from legacy core banking systems to hardware drivers. In fact, the simple C++ code often tends to be verbose.
When you are writing complex software, you are bound to get things wrong in any language, not just C++.
What I am arguing for is that C++ code need not be complex even if the C++ language itself is complex or the software is complex.
If you have an argument against this, please make it substantiatively without resorting to insinuation about the kind of work I do or not do.
I apologize if i sounded condescending, and coming from a BFSI background myself, it was such a relief to write software systems in Java after years of C++.
I agree "C++ code need not be complex", but the language itself is so designed that most often than not, you end up heading down a slippery slope.
This is not same for all cases. For example scientific and high performance computing, image processing, or other tasks which need short run times or applications where a single digit optimization results in hours of run time difference is always worth it.
Also, since C++ is native, very memory efficient data structures can be written. This property can be leveraged to write very small, very efficient and very resilient loops to be written which can run for months at a time, and since you have the absolute control over memory management, you can prevent unwanted bloating or leaks pretty easily.
Last, but not the least; CPUs weren't that powerful 10-15 years ago. My desktop computer was as fast as the first Raspberry Pi. Python, JS, even Java were very unpleasant experiences back then.
Well, you can spend 5 months writing your software in C++, and a month fixing your bugs and doing a bit of optimization. Or you can spend 3 months writing C# or other modern language and have another 3 months to optimize, all the while enjoying substantially faster compile times and super easy refactoring.
This is not an apples to apples comparison. I've given examples from runtime, not from development time.
OTOH, refactoring in C++ is not hard, at least from my experience. With some knowledge of the language, everyone can write reasonably bug-free C++ (or any other language) application in one go.
Compilation in C++ is a different story. C# is not native. It's converted to CLI (was that the name, I don't use Win32 for ~15 years), then JIT compiles it to machine code, and optimizes over and over in every execution.
Yes, C# is reasonably fast, but it's not native, cross platform (mono doesn't count), and designed to be a systems programming language.
People tend to think C++ is overly verbose, no. Win32 programming model forces simple operations to be long and time consuming for a programmer to implement. Opening a file, serial port, or anything is a single line, because of the (while not everyone likes it) "everything is a file" philosophy, which makes UNIX and C++ much easier, verbose, and accessible.
However, for simplicity I only thought about the out of the box experience, or by the de-facto implementation of the said languages, since it’s the most popular usage scenario as well.
I guess native needs to be qualified. I would say that Native means it speaks the ABI of and fits into a specific platform.
I would say that the C family languages are traditionally native of most operating systems [1] as they are polyglot and can directly speak whatever C ABI dialect is used there; they also work with the native platform tools.
C# is really native of the .Net platform and although it can be AoT compiled it either can't talk directly with Win32 or the interfacing is not seamless. Also it has its own set of tools.
Android and iOS are interesting because although they are clearly unix derivatives, the unix interface is not the primary interface; so arguably C and C++ are not really native there while Java and ObjectiveC/Swift respectively are.
[1] Or to be fair, of unix and any operating system that can be made to look like unix (Windows).
I think Rust and D have seamless integration with C style ABIs. Swift is native on iOS. Haskel and OCaml have, as far as I understand, painful FFIs and a relatively heavy runtime so I wouldn't call them native.
edit: to be clear, using 'compiles to object code' as the definition of Native, is both a misuse of the word, and meaningless as it is a implementation detail that can change easily and bound to be obsolete quickly.
I wouldn't want to be caught dead writing a GUI Windows app (I might do Qt if forced at gunpoint though), so I'm probably not the best person to test this :)
Also known as the credo of the software industry: why build something performant, robust, secure and usable when you can ship an Electron app that's barely fast enough and patch the bugs later?
I used to argue the same thing, but passing things across language barriers makes debugging painful and is usually pretty error prone, tools can't understand your codebase as well, and there are performance costs to all the conversions.
That only works if the surface area to volume ratio of the heavy lifting parts is low. If it isn't, you'll spend all your machine cycles crossing the bridge and human cycles maintaining the bridge rather than getting work done on either side.
Yes, there are alternatives, but when it comes to performance, expressiveness and actual deployability, C++ is pretty awesome.