29
votes

All programming languages are having their design flaws simply because not a single language can be perfect, just as with most (all?) other things. That aside, which design fault in a programming language has annoyed you the most through your history as a programmer?

Note that if a language is "bad" just because it isn't designed for a specific thing isn't a design flaw, but a feature of design, so don't list such annoyances of languages. If a language is illsuited for what it is designed for, that is of course a flaw in the design. Implementation specific things and under the hood things do not count either.

16
  • 6
    Note that this is constructive for language designers (mistakes to avoid), if someone would like to question how constructive this question is.
    – Anto
    Mar 5, 2011 at 16:08
  • Obvious dupe of programmers.stackexchange.com/questions/20985/…
    – greyfade
    Mar 5, 2011 at 16:33
  • 1
    @greyfade: Not really, this are about flaws in the actual language, that seems to be about things which lower adoption of a language, which could include bad standard library or just a bad website for the language. Some answers list e.g. bad syntax, but that isn't a specific design flaw
    – Anto
    Mar 5, 2011 at 16:54
  • 8
    Greatest flaw in any programming language? Humans. Mar 7, 2011 at 12:42
  • If these answers are the greatest flaws, then I'm really impressed by programming languages. Mar 12, 2011 at 19:27

49 Answers 49

42
votes
+100

One of my big annoyances is the way switch cases in C-derived languages default to falling through to the next case if you forget to use break. I understand that this is useful in very low level code (eg. Duff's Device), but it is usually inappropriate for application level code, and is a common source of coding errors.

I remember in about 1995 when I was reading about the details of Java for the first time, when I got to the part about the switch statement I was very disappointed that they had retained the default fall-through behaviour. This just makes switch into a glorified goto with another name.

23
  • 3
    @Christopher Mahan: switch doesn't have to work that way. For example, Ada's case/when statement (equivalent to switch/case) does not have fall-through behaviour. Mar 5, 2011 at 22:40
  • 2
    @Greg: switch-like statements in languages unrelated to C don't have to work that way. But if you use C-style control flow ({...}, for (i = 0; i < N; ++i), return, etc.), language inference will make people expect switch to work like C, and giving it Ada/Pascal/BASIC-like semantics would have confused people. C# requires break in switch statements for the same reason, although makes it less error-prone by forbidding silent fallthrough. (But I wish you could write fall; instead of the ugly goto case.)
    – dan04
    Mar 6, 2011 at 8:05
  • 4
    then don't write switch, write if() else. the reason you're complaining about is what makes switch better: it's not a true/false conditional, it's a numeral conditional, and that makes it different. You could also write your own switch function though.
    – jokoon
    Mar 6, 2011 at 11:40
  • 9
    -1 I consider it a benefit to allow fall through, there is no danger from it other than stupidity, yet it grants additional function.
    – Orbling
    Mar 8, 2011 at 0:25
  • 11
    The problem isn't that switch allows fallthrough. It's that most uses of fallthrough are not intentional.
    – dan04
    Mar 8, 2011 at 0:57
41
votes

I've never really liked the use of = for assignment and == for equality testing in C-derived languages. The potential for confusion and errors is too high. And don't even get me started on === in Javascript.

Better would have been := for assignment and = for equality testing. The semantics could have been exactly the same as they are today, where assignment is an expression that also produces a value.

15
  • 3
    @Nemanja Trifunovic: I originally thought of that suggestion, but it has an unfortunate ambiguity in C with less-than comparison against a negative number (ie. x<-5). C programmers wouldn't tolerate that kind of required whitespace :) Mar 6, 2011 at 2:35
  • 7
    @Greg: I would prefer := and == because it would be too easy to forget the : and not be notified like it's already the case (though reverted) when you forget a = today. I am thankful for compiler warnings on this... Mar 6, 2011 at 13:31
  • 13
    On almost all keyboards I've ever used, ":=" requires changing the shift key while typing it. On the one I'm using now, ':' is uppercase and '=' is lowercase, and I've had that reversed. I type a lot of assignments, and don't need that sort of typing hassle in them. Mar 7, 2011 at 19:00
  • 14
    @David Thornley: Code is read many more times than it is written. I don't buy any arguments about "typing hassle". Mar 7, 2011 at 19:06
  • 8
    @Greg Hewgill: Sure it's read more often than written. However, the problem between = and == isn't in reading, because they're distinct symbols. It's in writing and making sure you got the right one. Mar 7, 2011 at 19:45
29
votes

The choice of + in Javascript for both addition and string concatenation was a terrible mistake. Since values are untyped, this leads to byzantine rules that determine whether + will add or concatenate, depending on the exact content of each operand.

It would have been easy in the beginning to introduce a completely new operator such as $ for string concatenation.

14
  • 13
    @Barry: Not really. + makes a lot of sense as a string concatenation operator in a strongly typed language. The problem is that Javascript uses it but is not strongly typed. Mar 6, 2011 at 6:02
  • 13
    @Mason: + does not make sense for concatenation, because concatenation is definitely not commutative. It's an abuse as far as I am concerned. Mar 6, 2011 at 13:36
  • 12
    @Matthieu: Umm... why does that matter? Concatenation isn't commutative (like addition is), but the addition of two strings is nonsensical so no one thinks of it that way. You're inventing a problem where none exists. Mar 6, 2011 at 13:49
  • 4
    just switch to C++ and add any kind to esoteric overloading to the operator of your choice
    – Newtopian
    Mar 7, 2011 at 5:50
  • 8
    @Matthieu: Commutativity isn't the issue here. If JS had a Matrix class, would you consider it an abuse for it to have an * operator?
    – dan04
    Mar 8, 2011 at 0:56
24
votes

I find Javascript default to global to a major issue, and often source of bugs if you don't use JSLint or the like

8
  • 2
    Default to global? I'm glad I don't use JS then...
    – Anto
    Mar 5, 2011 at 16:31
  • 5
    Actually it is very nice language, with a few bad design choices in it. This is the major one, if you don't scope a variable it will scope it as a global. The good thing is that by using Doug Crockford's Jslint program you can catch this error and a bunch more.
    – Zachary K
    Mar 5, 2011 at 16:36
  • 1
    Just use var whenever you declare a variable and you are good to go. And don't tell me it's too much typing because Java forces you to declare all the types twice and nobody complains about it being a shitty design choice.
    – user7146
    Mar 6, 2011 at 6:10
  • 3
    @davidk01: People do complain about that. Similarly, people complained about having to declare std::map<KEY, VALUE>::const_iterator variables in C++ enough that auto is being added to that language.
    – dan04
    Mar 6, 2011 at 7:03
  • 1
    the "use strict" directive, added in es5, changes undeclared references into an error. Oct 7, 2011 at 15:14
22
votes

The preprocessor in C and C++ is a massive kludge, creates abstractions that leak like sieves, encourages spaghetti code via rat's nests of #ifdef statements, and requires horribly unreadable ALL_CAPS names to work around its limitations. The root of these problems is that it operates at the textual level rather than the syntactic or semantic level. It should have been replaced with real language features for its various use cases. Here are some examples, though admittedly some of these are solved in C++, C99 or unofficial but de facto standard extensions:

  • #include should have been replaced with a real module system.

  • Inline functions and templates/generics could replace most of the function call use cases.

  • Some kind of manifest/compile time constant feature could be used for declaring such constants. D's extensions of enum work great here.

  • Real syntax tree level macros could solve a lot of miscellaneous use cases.

  • String mixins could be used for the code injection use case.

  • static if or version statements could be used for conditional compilation.

9
  • 2
    @dsimcha: I agree with the #include issue, but the module system was invented... afterward! And C and C++ aim for a maximum of backward compatibility :/ Mar 6, 2011 at 13:34
  • @Matthieu: Yes, module systems were invented afterwords, but we're talking about hindsight here anyhow.
    – dsimcha
    Mar 6, 2011 at 15:13
  • 3
    I agree that this is a massive pain in various body parts, but the multi-pass design has the advantage of simplicity: a C compiler doesn't need to know much about the context of operation before it can successfully compile a chunk of C code. This can only be qualified as a design error if you can show that the costs of using a hypothetical module system within the C language itself (e.g. C++-like classes) is always lower than or comparable to the present cpp-based #include hacking. Mar 8, 2011 at 9:00
  • I agree. However, but some people think that preprocessing is a good thing, and complain that it is not supported in (for example) Java.
    – Stephen C
    Mar 10, 2011 at 1:49
  • 5
    @Stephen: I agree that Java with a preprocessor might be better than Java without, but only because Java doesn't have several of the "real" features necessary to replace the preprocessor. In languages like D, which include such features and Python, which gets flexibility in other ways by being dynamic, I don't miss it one bit.
    – dsimcha
    Mar 10, 2011 at 3:55
21
votes

One could list hundreds of mistakes in hundreds of language, but IMO it is not a useful exercise from a language design perspective.

Why?

Because something that would be a mistake in one language would not be a mistake in another language. For instance:

  • Making C a managed (i.e. garbage collected) language or tying down the primitive types would limit its usefulness as a semi-portable low-level language.
  • Adding C-style memory management to Java (for example, to address performance concerns) would break it.

There are lessons to be learned, but the lessons are rarely clear cut, and to understand them you have to understand the technical trade-offs ... and the historical context. (For instance, the cumbersome Java implementation of generics is a consequence of an overriding business requirement to maintain backwards compatibility.)

IMO, if you are serious about designing a new language, you need to actually use a wide range of existing languages (and study historical languages) ... and make up your own mind what the mistakes are. And you need to bear in mind that each of these languages was designed in a particular historical context, to fill a particular need.

If there are general lessons to be learned they are at the "meta" level:

  • You cannot design a programming language that is ideal for all purposes.
  • You cannot avoid making mistakes ... especially when viewed from hind-sight.
  • Many mistakes are painful to correct ... for users of your language.
  • You have to take account of the background and skills of your target audience; i.e. existing programmers.
  • You cannot please everyone.
3
  • 9
    -1 -- programming languages follow design goals. A features of a language that work against these goals is a design flaws, unless it is a necessary compromise for one of its other goals. Not many languages are made with the intention of satisfying everyone, but all languages should attempt to satisfy those people that it sets out to satisfy in the first place. This kind of postmodernistic political correctness is quite stifling to programming language research and development. Mar 9, 2011 at 22:08
  • My point is that it is difficult to learn lessons without taking into account the design goals.
    – Stephen C
    Mar 10, 2011 at 1:47
  • 1
    seems to me that the OP already accounted for your answer in the second paragraph of the question. Mar 10, 2011 at 9:14
20
votes

C and C++: All those integer types that don't mean anything.

Especially char. Is it text or is it a tiny integer? If it's text, is it an "ANSI" character or a UTF-8 code unit? If it's an integer, is it signed or unsigned?

int was intended to be the "native"-sized integer, but on 64-bit systems, it isn't.

long may or may not be larger than int. It may or may not be the size of a pointer. It's pretty much an arbitrary decision on the part of the compiler writers whether it's 32-bit or 64-bit.

Definitely a language of the 1970s. Before Unicode. Before 64-bit computers.

15
  • 10
    C was not designed to be a standard fits-all language, hence it cannot be a design error. It was designed to model a cpu as portable assembler avoiding cpu-specific assembler code. However, it was fixed in Java.
    – user1249
    Mar 5, 2011 at 17:10
  • 7
    I think the bigger problem is newer languages that continue to use the same meaningless terms, despite history showing us it's a terrible idea. At least the C guys noticed their mistake and created standard int types.
    – Mark H
    Mar 5, 2011 at 17:48
  • 5
    A char is not a utf-8 unit. A utf-8 character can take more that 8 bits to store. C is not a language of the 1970s, I'm using it for a project now (voluntarily). Mar 5, 2011 at 18:45
  • 4
    C is little more than a high-level abstraction of the PDP-11 processor. For example, pre and post incrementation were directly supported by the PDP-11. Mar 5, 2011 at 22:32
  • 5
    This is a terribly misguided answer. First off, C and C++ are not interchangeable. Second, the language spec clearly defines what a char is - An object declared as type char is large enough to store any member of the basic execution character set.. Third, C is not a "language of the 70's", it is a language that lives close to the hardware and is probably the language that ultimately allows all of your high level abstractions to actually make sense to a CPU. You come off as a person who knows only high level languages and has no appreciation for how things actually work. -1 Mar 5, 2011 at 23:18
18
votes

null.

Its inventor, Tony Hoare, calls it the "billion dollar mistake".

It was introduced in ALGOL in the 60s, and exists in most of the commonly used programming languages today.

The better alternative, used in languages like OCaml and Haskell, is the maybe. The general idea is that object references cannot be null/empty/non-existent unless there's an explicit indication that they may be so.

(Although Tony's awesome in his modesty, I think almost anyone would have made the same mistake, and he just happened to be first.)

7
  • Disagree, even with its inventor !!! null is the empty value for the pointer / reference data type. Strings has empty string, sets has empty set (Pascal empty set = []), integers has 0. Most programming languages that use null / nil / whatever, if a variable is assigned properly a null error can be prevented.
    – umlcat
    Mar 10, 2011 at 18:52
  • 4
    @user14579: Every language that supports any kind of set or string or array has {}, but such is still semantically appropriate and will not crash unless you already have something that could possibly cause an array bounds error -- but that's another issue. You can process an empty string to uppercase all the characters, which will result in an empty string. You try the same thing on a null string, and without proper consideration, it'll crash. The problem is that this proper consideration is tedious, often forgotten, and makes it difficult to write single-expression functions (i.e. lambdas). Mar 10, 2011 at 20:15
  • 1
    I make money every time I type null...oh right someone loses money every time I type null. Except this time.
    – kevpie
    Mar 12, 2011 at 12:59
  • 3
    @umlcat - when you have languages with pattern matching like Ocaml, Haskell, and F#, using the Maybe x | None pattern prevents you from forgetting the null case at compile-time. No amount of compile-time trickery can catch an error in languages where null is the established idiom. Since you have to explicitly choose not to deal with the null case in languages that have the Maybe and Some monad, they have a serious advantage over the "null" approach.
    – JasonTrue
    Mar 12, 2011 at 22:27
  • 1
    @Jason -- I like to think of maybe as a null opt-in, whereas the null exception is an opt-out. Of course there's some things to be said about the difference between runtime errors and compile-time errors too, but just the fact that null is essentially injecting behavior is noteworthy in itself. Mar 13, 2011 at 0:11
14
votes

I get the feeling that the people who designed PHP didn't use a normal keyboard, they don't even use a colemak keyboard, because they should have realized what they were doing.

I am a PHP developer. PHP isn't fun to type.

Who::in::their::right::mind::would::do::this()? The :: operator requires holding shift and then two key presses. What a waste of energy.

Although->this->is->not->much->better. That also requires three key presses with the shift being in between the two symbols.

$last = $we.$have.$the.$dumb.'$'.$character. The dollar sign is used a tremendous amount of times and requires the award stretch up to the very top of the keyboard plus a shift key press.

Why couldn't they design PHP to use keys that are much faster to type? Why couldn't we.do.this() or have vars start with a key that only requires a single keypress - or non at all (JavaScript) and just pre-define all vars (like I have to do for E_STRICT anyway)!

I'm no slow typist - but this is just a lame design choice.

5
  • Perl shares this pain.
    – Daenyth
    Mar 9, 2011 at 20:52
  • 2
    So does C++, and for some inexplicable bizarre reason, PowerShell. Mar 9, 2011 at 20:53
  • 2
    Use a more powerful editor, and define your own key sequences for those operators. Mar 9, 2011 at 21:14
  • 1
    I::have::nothing::against::this->at.all() Mar 12, 2011 at 22:25
  • 1
    Maybe they don't use qwerty keyboards. $ and : don't need the shift key pressed on all the azerty like keyboards.
    – Arkh
    Oct 7, 2011 at 15:55
13
votes

The use of desktop inspired forms within asp.net.

It always felt a fudge and got in the way or the how the web actually works. Thankfully asp.net-mvc does not suffer in the same way, though with credit to Ruby etc for that inspiration.

6
  • 18
    Isn't that a library thing?
    – nikie
    Mar 5, 2011 at 16:16
  • @nikie that's a good point;)
    – dove
    Mar 5, 2011 at 16:34
  • 1
    @nikie Actually, the XML-based ASPX code is a language, so you can twist this one to work. :D Mar 9, 2011 at 21:58
  • 2
    The real problem with ASP.NET, I think, is how hard it tries to hide the details of the web from the programmer. There's actually some really neat, useful stuff going on in ASP.NET, but you have to fight so hard and dig so deep to get at it. Mar 9, 2011 at 21:59
  • 1
    On the other hand there are thousands and thousands of simple and successful data collection apps out there that where put together using the "classic" desktop app thing. The only bad thing was that until MVC the only Microsoft option was the windows forms. Mar 12, 2011 at 2:27
13
votes

For me it is PHP's absolute lack of naming and argument ordering conventions in its standard library.

Though JASS's necessity to nullify references after the referenced object was released/removed (or the reference would leak and several bytes of memory would be lost) is more serious, but since JASS is single purpose language, it is not that critical.

3
  • 9
    The lack of conventions in PHP's stdlib is arguable not a language design flaw.
    – user7043
    Mar 5, 2011 at 16:15
  • 3
    @delnan: The lack of conventions is a result of how PHP was designed, and therefore has a lot to do with the language design. Also it is not clear to me that there is a clear distinction between libraries and language. Lisp in particular has a proud tradition of bootstrapping one language on top of another.
    – btilly
    Mar 8, 2011 at 17:35
  • 1
    The truly remarkable thing about JASS was that it had reference counting on handles, but wouldn't clean them up unless they were manually destroyed (and the graphic interface created functions that leaked memory everywhere)! Oct 7, 2011 at 17:39
13
votes

The greatest design flaw that I face is that python was not designed like python 3.x to begin with.

4
  • 1
    Well, not even Guido can get everything right at once...
    – user7043
    Mar 5, 2011 at 21:12
  • 5
    @delnan, oh I know, and python < 3 is still a staggeringly good language, but it is a little annoying to have better language in the form of python 3.x that I can't use because it breaks all of the modules that I need. Mar 6, 2011 at 6:19
  • Continue to lobby for your python 3.x modules! Meanwhile I will keep writing in 2.5.4. Thanks to SO I actually am reminded that 3.x is alive and well.
    – kevpie
    Mar 12, 2011 at 12:55
  • @kevpie First, lobby to add conditional compilation to python to make the transition easier for library maintainers. 2to3 is not a maintainable solution in the long term. Mar 18, 2011 at 17:16
12
votes

Array decay in C and consequently C++.

5
  • I wish for proper array support too. In C++ you can prevent decay using abscons syntax and templates... but it's more of a hack :/ Mar 6, 2011 at 13:38
  • 1
    Note that this is the reason that C++ has to have separate delete and delete[] operators.
    – dan04
    Mar 8, 2011 at 2:35
  • You can always put an array into a struct and have it passed around by value if you like, but this is usually more awkward than the original problem. In C++, you can usually avoid the need to use arrays. Mar 8, 2011 at 15:40
  • 2
    At least in the C case, the argument against proper array support is "array bounds checking is expensive", particularly given the way that C pointer arithmetic works.
    – Stephen C
    Mar 10, 2011 at 1:51
  • @Stephen C: What array bounds checking has to do with array decay? Mar 10, 2011 at 3:35
11
votes

Primitive types in Java.

They break the principle that everything is a descendant of java.lang.Object, which from a theoretical point of view leads to additional complexity of the language specification, and from a practical perspective they make the use of collections extremely tedious.

Autoboxing helped alleviate the practical drawbacks but at the cost of making the specification even more complicated and introducing a big fat banana skin: now you can get a null pointer exception from what looks like a simple arithmetic operation.

2
  • It would cause terrible performance issue if you removed primitives types. And autoboxing can be messed up pretty easily, so it improves nothing.
    – deadalnix
    Jun 5, 2011 at 0:02
  • At the time, this was a sensible decision from the Java designers. The performance gains given the machines / VMs available in the 90s outweighed the conceptual advantages of unifying everything around java.lang.Object.
    – mikera
    Oct 8, 2011 at 4:14
10
votes

I know Perl best, so I'll pick on it.

Perl tried many ideas. Some were good. Some were bad. Some were original and not widely copied for good reason.

One is the idea of context - every function call takes place in list or scalar context, and can do entirely different things in each context. As I pointed out at http://use.perl.org/~btilly/journal/36756 this complicates every API, and frequently leads to subtle design issues in Perl code.

The next is the idea of tying syntax and data types so completely. This lead to the invention of tie to allow objects to masquerade as other data types. (You can also achieve the same effect using overload, but tie is the more common approach in Perl.)

Another common mistake, made by many languages, is to start off by offering dynamic scoping rather than lexical. It is hard to revert this design decision later, and leads to long-lasting warts. The classic description of those warts in Perl is http://perl.plover.com/FAQs/Namespaces.html. Note that this was written before Perl added our variables and static variables.

People legitimately disagree on static versus dynamic typing. I personally like dynamic typing. However it is important to have enough structure to let typos to be caught. Perl 5 does a good job of this with strict. But Perl 1-4 got this wrong. Several other languages have lint checkers that do the same thing as strict. As long as you are good about enforcing lint checking, that is acceptable.

If you're looking for more bad ideas (lots of them), learn PHP and study its history. My favorite past mistake (long ago fixed because it lead to so many security holes) was defaulting to allowing anyone to set any variable by passing in form parameters. But that is far from the only mistake.

8
  • 5
    Yea, Perl has a lot of mistakes, because the folks who built it were trying new ideas, and when you do that you often get them wrong. (Perl also has some very good stuff, and is the standard for Regexps that everyone else seems to have copied)
    – Zachary K
    Mar 5, 2011 at 17:04
  • @zachary-k: Absolutely agreed. And I tried to make that clear before I began walking through issues.
    – btilly
    Mar 5, 2011 at 17:39
  • 4
    Lisps were originally dynamically scoped, and over time got changed to being lexically scoped (at least in Scheme and Common Lisp). It's not impossible to change. Mar 7, 2011 at 19:07
  • 4
    @david-thornley: It is impossible unless you sacrifice backwards compatibility somewhere. Scheme was always lexically scoped. Common Lisp was lexically scoped from the time it was standardized, but various Lisp communities had their struggles adopting it. And Emacs Lisp is still using dynamic scoping, even though there has been a desire to change it for a long time.
    – btilly
    Mar 7, 2011 at 19:41
  • 1
    BTW, many of the things people dislike Perl for weren't invented in Perl but taken from other languages, mostly the Bourne shell. Mar 8, 2011 at 9:05
10
votes

JavaScripts ambiguity for code blocks and object literals.

  {a:b}

could be a code block, where a is a label and b is an expression; or it could define an object, with an attribute a which has the value b

4
  • I actually like this about JavaScript. Language structure simplicity is nice and the purpose should be evident if the developer knows what they are doing.
    – Xeoncross
    Mar 10, 2011 at 19:12
  • 2
    Xeoncross: I generally dislike ambiguities. In this case, it's obvious to the developer, but eval() needs extra parentheses.
    – user281377
    Mar 10, 2011 at 20:51
  • 2
    @ammoQ: It's obvious? What is it then? An object or a code block? Oct 7, 2011 at 17:24
  • configurator: Obviously an object. No sane person would use a label called a.
    – user281377
    Oct 10, 2011 at 7:24
10
votes

I'm going to go back to FORTRAN and whitespace insensitivity.

It pervaded the specification. The END card had to be defined as a card with an 'E', an 'N', and a 'D' in that order in columns 7-72, and no other nonblanks, rather than a card with "END" in the proper columns and nothing else.

It led to easy syntactic confusion. DO 100 I = 1, 10 was a loop control statement, while DO 100 I = 1. 10 was a statement that assigned the value 1.1 to a variable called DO10I. (The fact that variables could be created without declaration, their type depending on their first letter, contributed to this.) Unlike other languages, there was no way to use spaces to separate out tokens to allow disambiguation.

It also allowed other people to write really confusing code. There's reasons why this feature of FORTRAN was never duplicated ever again.

10
  • 1
    In a language where you can redefine literals that's the worst example - I mean it only caused one tiny little spacecraft to crash Mar 8, 2011 at 0:10
  • Early on, you could write DAMNATION in place of DIMENSION, and it would work. Mar 8, 2011 at 14:32
  • It's still being taught, by people outside of CS. I still have to deal with those who a) fight against declarations, b) fight against whitespace, c) like "continuation lines", d) use 6-character names, or 4, d) are stumped when they see (test ? a : b), e) insist on using **, f) can't handle case-sensitivity. Most of this was because of keypunches in the 50s. Mar 8, 2011 at 14:38
  • 1
    @Martin Beckett - redefining literals in FORTRAN was really a compiler flaw rather than a language feature. It certainly wasn't an intentional language feature.
    – Stephen C
    Mar 10, 2011 at 1:56
  • 1
    @oosterwal: I certainly did. I might be wrong, but I vaguely remember the language definition based on punch cards. They were the main way of inputting FORTRAN programs back then, and the idea of an 80-column line with columns 73-80 reserved is from punch cards. Mar 10, 2011 at 14:30
9
votes

One of the biggest issues with BASIC was the lack of any well defined method to extend the language beyond it's early environments, leading to a bunch of completely incompatible implementations (and a nearly irrelevant post-facto attempt at any standardization).

Almost any language will get bent into general purpose use by some crazy programmer. It's better to plan for that general purpose usage at the beginning in case that crazy idea takes off.

1
  • 2
    +1: Any language without proper modules and libraries is a mistake waiting to happen. COBOL suffered from this, also, leading to peculiar variants that aren't compatible.
    – S.Lott
    Mar 10, 2011 at 16:07
8
votes

I believe in DSLs (domain-specific-languages) and one thing I value in a language is if it allows me to define a DSL on top of it.

In Lisp there are macros - most people consider this a good thing, as do I.

In C and C++ there are macros - people complain about them, but I was able to use them to define DSLs.

In Java, they were left out (and therefore in C#), and the lack of them was declared to be a virtue. Sure it lets you have intellisense, but to me that's just an oeuvre. To do my DSL, I have to expand-by-hand. It's a pain, and it makes me look like a bad programmer, even though it lets me do a heck of a lot more with tons less code.

10
  • 4
    I'd agree that any language without decent macros is a one huge unfixable design flaw. But what do you mean by 'they were left out'? C preprocessor was not any kind of a decent macro system. Java is not derived from any proper language with macros.
    – SK-logic
    Mar 6, 2011 at 9:04
  • 1
    You can write your DSL in an external macro processing language (like m4, say, among a myriad of others). Mar 6, 2011 at 10:42
  • 4
    @SK: I won't say the C preprocessor is a decent macro system compared to Lisp (for example). But, compared to nothing, it's extremely useful. Mar 6, 2011 at 15:46
  • 4
    @reinierpost: I'm thinking of things I could do in Lisp, such as introduce control structures like differential execution and backtrack. These could be done with Lisp macros. In C/C++ I could do differential execution with C macros (and a little programmer discipline), but not backtrack. With C# I can do neither. What I get in exchange is things like intellisense. BFD. Mar 8, 2011 at 12:57
  • 1
    @David: The way I did it was I had a macro to wrap around ordinary code, such as a list of statements. It would take the cdr of the list and form a lambda closure out of it (i.e. a continuation) and pass it as an argument to the car of the list. That was done recursively, of course, and would "do the right thing" for conditionals, loops, and function calls. Then the "choice" function just turned into a normal loop. Not pretty, but it was robust. Problem is, it makes it super easy to make overly-nested loops. Oct 7, 2011 at 21:16
7
votes

Statements, in every language that has them. They do nothing that you can't do with expressions and prevent you from doing lots of things. The existence of a ?: ternary operator is just one example of having to try to get around them. In JavaScript, they are particularly annoying:

// With statements:
node.listen(function(arg) {
  var result;
  if (arg) {
    result = 'yes';
  } else {
    result = 'no';
  }
  return result;
})

// Without:
node.listen(function(arg) if (arg) 'yes' else 'no')
13
  • I'm confused here: Do you just want a simpler way to do things?
    – TheLQ
    Mar 8, 2011 at 1:38
  • 2
    Correct. Expressions for everything.
    – munificent
    Mar 8, 2011 at 6:11
  • 1
    Lisp works well for this. Mar 8, 2011 at 15:50
  • 1
    @SK-logic: I suspect that statements were blindly inherited from machine language, through FORTRAN, ALGOL, and COBOL. Mar 8, 2011 at 21:15
  • 1
    I'm pretty sure machine language is the common ancestor, and that's just a reflection of the fact that modern computers based on von Neumann architecture execute instructions sequentially and modify state. Ultimately when IO happens, there are going to be expressions that don't yield meaningful data, so statements aren't entirely useless in indicating semantically that some code has only side effects. Even languages that have a notion of unit type (aka ()) instead of statements have special consideration to ensure that they don't throw warnings or otherwise behave strangely. Mar 9, 2011 at 21:03
6
votes

For me, it is the design problem that plagues all of the languages that were derived from C; namely, the "dangling else." This grammatical problem should have been resolved in C++, but it was carried forth into Java and C#.

13
  • 3
    One of the core goals of C++ was to be fully backward compatible with C. If they had drastically changed semantic behavior it may not have caught on like it did (or at least, that was the thought at the time) Mar 5, 2011 at 23:31
  • 2
    @Ed S., however, elimination of the "dangling else" problem could have been accomplished by eliminating the <compound_statement> (a.k.a. <block>) grammatical production and incorporating the curly braces into the conditional and iterative control structures like they did when they added the the try/catch exception handling control structure. There is no excuse for not rectifying this grammatical ambiguity in Java and C#. Currently, the defensive work around for this grammatical ambiguity is to make every statement that follows a conditional or iterative control statement a compound statement. Mar 6, 2011 at 0:19
  • 1
    What do you mean by that? This isn’t a “grammatical problem” (it’s completely unambiguous). How would you “resolve” it? I actually find the rules in C satisfactory. Arguably, only a Python-like syntax (= meaningful indentation) can really solve this problem. Furthermore, I’m actually very happy that modern languages do not mandate braces. I agree that all C-like syntaxes suck but dangling-else is the least of their problems. Mar 6, 2011 at 16:51
  • 1
    Continuing: I think that Python's use of a indentation as means by which to delineate a statement list is weird beyond belief. This technique violates the "separation of concerns" principle by tightly-coupling lexical scanning with syntax analysis. A context-free grammar should be able to be parsed without knowing anything about the layout of the source. Mar 6, 2011 at 19:33
  • 3
    @bit-twiddler: No, it doesn't. The Python lexer just converts the whitespace to the appropriate INDENT and DEDENT tokens. Once that's done, Python has a pretty conventional grammar (docs.python.org/reference/grammar.html).
    – dan04
    Mar 8, 2011 at 0:53
6
votes

I think all the answers so far point to a single failing of many mainstream languages:

There is no way to change the core language without affecting backward compatibility.

If this is solved then pretty much all those other gripes can be solved.

EDIT.

this can be solved in libraries by having different namespaces, and you could conceive of doing something similar for most of the core of a language, though this might then mean you need to support multiple compilers/interpreters.

Ultimately I don't think I know how to solve it in a way that is totally satisfactory, but that doesn't mean a solution doesn't exist, or that more can't be done

12
  • 1
    How would you go about "solving" this? Mar 9, 2011 at 18:36
  • I'm not sure it can be totally solved - obviously keeping things out of the core language and in a standard library helps so I think I'd try to push this as far as it could go
    – jk.
    Mar 9, 2011 at 21:05
  • 1
    By backwards compatible, do you mean newer compilers should be able to compile old code? Mar 10, 2011 at 1:08
  • I mean newer compilers shouldn't change the meaning of old code, failing to compile would be a subset of that
    – jk.
    Mar 10, 2011 at 9:04
  • most of cases, when a particular feature doesn't exist, or want to change, people make a new language based in previous one. C with classes => C++
    – umlcat
    Mar 10, 2011 at 18:54
4
votes

Java's silent integer arithmetic overflow

2
  • 4
    Java was far from the first language to have that feature.
    – dan04
    Mar 6, 2011 at 7:41
  • @dan04 It is a very nice feature, isn't it? Oct 7, 2011 at 23:18
4
votes

Both Java and C# have annoying problems with their type systems due to the desire to maintain backwards compatibility while adding generics. Java doesn't like mixing generics and arrays; C# won't allow some useful signatures because you can't use value types as bounds.

As an example of the latter, consider that

public static T Parse<T>(Type<T> type, string str) where T : Enum
alongside or replacing
public static object Parse(Type type, string str)
in the Enum class would allow
MyEnum e = Enum.Parse(typeof(MyEnum), str);
rather than the tautological
MyEnum e = (MyEnum)Enum.Parse(typeof(MyEnum), str);

tl;dr: think about parametric polymorphism when you start designing your type system, not after you publish version 1.

1
  • 2
    The inability to restrict types to enum is annoying in C#, but you can kind of work around it like this MyMethod<T>(T value) where T : struct, IComparable, IFormattable, IConvertible But you still have to test for an enum and it's a hack. I think the bigger lack in the C# generics is no support for higher kinds, which would really open up the language to some cool concepts. Mar 9, 2011 at 22:10
4
votes

ALTER

When I learned COBOL, the ALTER statement was still a part of the standard. In a nutshell, this statement would allow you to modify procedure calls during runtime.

The danger was that you could put this statement in some obscure section of code that was rarely accessed and it had the potential to completely change the flow of the rest of your program. With multiple ALTER statements you could make it nearly impossible to know what your program was doing at any point in time.

My university instructor, very emphatically, stated that if he ever saw that statement in any of our programs he would automatically flunk us.

1
  • It does have good use cases though - stubbing or memoization. Instead of writing v() { if (not alreadyCalculatedResult) { result = long(operation); alreadyCalculatedResult = true; } result; } you say v() { result = long(operation); v = () => result; result; } Oct 7, 2011 at 17:36
4
votes

The worst sin of a programming language is not being well defined. A case I remember is C++, which, in its origins:

  1. Was so ill defined that you could not get a program to compile and run by following books or examples.
  2. Once you tweaked the program to compile and run under one compiler and OS, you'd have to start over if you switched compilers or platforms.

As I remember, it took about a decade to get C++ defined well enough as to make it as professionally dependable as C. It's something that should never happen again.

Something else I consider a sin (should it go in a different answer?) is having more than one "best" way to do some common task. It is the case of (again) C++, Perl, and Ruby.

4
  • 1
    I don't see how to avoid ill-definedness in an evolving language. Or, for that matter, in a predesigned language where the original designer missed some important points (like Pascal). Mar 11, 2011 at 21:02
  • @David Thornley Well-definedness is well defined. Bugs withstanding, most programming language designers get it right from the start. Tools can check that a grammar is unambiguous when it's complete (C++ requires at least three grammars), and the semantics should for standard implementations should be specified.
    – Apalala
    Mar 13, 2011 at 1:04
  • I agree that well-defined languages are possible, but that's not going to happen in an evolving language like pre-Standard C++. The alternative is that each language is thoroughly designed before release, and that's not necessarily the way to get the best languages. I'd say that most programming language designers get things wrong at the start, since language design is extremely complicated. Mar 13, 2011 at 23:19
  • I guess I'm having trouble understanding what you mean by "well defined". Is your complaint that different C++ compilers didn't actually compile the same language? Oct 7, 2011 at 15:24
3
votes

Classes in C++ are some kind of forced design pattern in the language.

There is practically no difference at runtime between a struct and a class, and it is so confusing to understand what is the real true programming advantage of "information hiding" that I want to put it there.

I'm going to be downvoted for that, but anyway, C++ compilers are so hard to write this language feels like a monster.

5
  • 2
    Information hiding is important because it lets you hide implementation specific details, which are likely to change, from the accessible parts of the API (the "UI" of the API) thus making changes to the program becomes easier and less painful.
    – Anto
    Mar 6, 2011 at 20:57
  • 1
    The UI of the API... no, seriously, I don't buy it.
    – jokoon
    Mar 6, 2011 at 21:36
  • 3
    This difference is not the most revolting part of C++, not even close. The only difference is a default access modifier (public for structs, private for classes). C++ is a horrible, monstrous language, but certainly not in this part.
    – SK-logic
    Mar 7, 2011 at 12:45
  • sk-logic: well, I could say the horrors start there.
    – jokoon
    Mar 7, 2011 at 13:46
  • 2
    Information hiding is good; you can find discussions of that all over. The only prominent software book I can think of that was against it was Brooks' "The Mythical Man-Month", and he later considered it the biggest mistake in the book. If you don't understand the advantages, you really aren't qualified to make the judgment you're making. Mar 7, 2011 at 19:10
3
votes

I feel like I'm opening myself up to get flamed, but I really hate the ability to pass plain old data types by reference in C++. I only slightly hate being able to pass complex types by reference. If I'm looking at a function:

void foo()
{
    int a = 8;
    bar(a);
}

From the calling point, there is no way to tell that bar, which may be defined in a completely different file, is:

void bar(int& a)
{
    a++;
}

Some might argue that doing something like this may just be a bad software design, and not to blame the language, but I don't like that the language lets you do this in the first place. Using a pointer and calling

bar(&a);

is much more readable.

7
  • +1 I don't agree with you, but I appreciate your reasoning.
    – Jon Purdy
    Mar 9, 2011 at 22:12
  • @Jon I'd actually be very interested on what you think. Do you carry the "don't blame the language" view?
    – Jeff
    Mar 9, 2011 at 22:20
  • 6
    @Jeff: For one thing, the primary reason that reference semantics made their way into C++ was operator overloading, for which uniform reference behaviour simply makes sense. More importantly, though, C++ is designed to be versatile and provide very fine-grained features, even if doing so incurs significant risk of programmer error. So yeah, at least in this particular case, don't blame the language. I'd rather be able to make mistakes than let a language get in my way.
    – Jon Purdy
    Mar 10, 2011 at 4:47
  • @Jon agreed, it would very odd for pass by reference to apply to everything besides PODs. I'd prefer it if this feature was missing entirely from C++ as an alternative, but we can agree to disagree :). Thanks for the input!
    – Jeff
    Mar 10, 2011 at 5:54
  • Java seems to not like pointers as much as you do. Mar 12, 2011 at 22:35
3
votes

Although every language has it's faults, none are nuisances once you know about them. Except for this pair:

Complex syntax coupled with a wordy APIs

This is particularly true of a language like Objective-C. Not only is the syntax overwhelmingly complex but the API uses function names like:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

I'm all for being explicit and un-ambiguous, but this is ridiculous. Each time I sit down with xcode, I feel like a n00b, and that's really frustrating.

2
  • The Objective-C syntax is overwhelmingly complex? Haven't you seen C++? And the actual method name there is tableView:cellForRowAtIndexPath:, which is very descriptive in my opinion.
    – user4595
    Oct 7, 2011 at 23:28
  • Learn to touch-type.
    – finnw
    Nov 21, 2011 at 2:57
2
votes
  1. signed chars in C - an abomination invented to let mathematicians have big collections of small items
  2. Using case to carry semantic content - again for mathematicians, who don't need to talk, and never have enough space for their formulas
  3. Let/Plain assignment vs. Set assignment in Basic dialects - no mathematicians involved here I think
9
  • I'm lost as to your signed chars comment, could you explain? Mar 5, 2011 at 22:27
  • 1
    To a human, the concept of (un)signed chars and the necessity to tell the compiler to use unsigned chars as default is surely just as insane as to a mathematician the claim that 2 != 2, because the second 2 is uppercase, bold, or italic. Mar 5, 2011 at 23:16
  • 5
    The problem is that C confounds the concept of "char" (i.e., part of a text string) and "byte" (i.e, (u)int_least8_t). Signedness makes perfect sense for small integers, but no sense at all for characters.
    – dan04
    Mar 6, 2011 at 7:16
  • @dan04: I agree, I wish they had different types for small integers (signed and unsigned), byte and character. When you explain to newbies that to manipulate raw memory they need to cast to a char*... like a C-String, they get really confused. Mar 6, 2011 at 13:40
  • C# got this right with separate sbyte, byte, and char types.
    – dan04
    Mar 6, 2011 at 19:06

Not the answer you're looking for? Browse other questions tagged or ask your own question.