Making Wrong Code Look Wrong
joelonsoftware.com
joelonsoftware.com
Every program is going to have some kind of semantic problem that cannot easily be fixed with compile-time analysis. Unsafe strings in web applications are merely one example for one class of programs written in one class of languages. If you grasp what he’s saying about coding conventions, ignore the example.
So to his actual point: Do you agree with my contention that there are always going to be some semantically wrong bits of code that cannot be automatically detected by the compiler, or that it is awkward to do so?
If so, do you think coding conventions help more than they hinder? In other words, do they increase code safety without imposing too much of a burden on readability?
It seems to me that answering this question is more or less exactly "arguing whether Haskell’s typing beats Hungarian notation," although of course Haskell type systems aren’t the only possible alternative. For example, any number of templating systems in languages like Python use late-bound method calls and run-time type checks, instead of static typing, to avoid cross-site scripting errors.
The problem, to me, is that Hungarian notation (and other coding conventions) only helps you detect semantic errors in cases where the errors can be detected by shallow, local analysis — exactly the kinds of errors that can be detected automatically by a program, either at compile-time or run-time.
So, what do we do about the deeper errors? Well, I don't know. I don't think abandoning coding conventions is a solution — I still want the code I read to be written in a predictable style, so that the variation in it is semantically meaningful — but I don't think coding conventions can do a better job than software of detecting errors.
However, if I were trying to write a GUI on a 1-MIPS machine with 1 MiB of RAM in the late 1980s, it might work better to do a bunch of that checking by hand (with Hungarian) than to try to invent a better programming language that does the checking for me automatically.
And surely bringing up something like Haskell is germane? The article implies that this is the best/only/right way of dealing with issues like this, when that definitely isn't true for languages with a different/stronger type system, as one case.
Of course, this is pure opinion and supposition on my part - I suspect a proof either way is NP-BloodyImpossible. I do feel like coding conventions such as those he suggests make code harder to read, even if only a little bit. And that's ignoring IDE support as well - I feel the same if I'm in Emacs. If there are enough things in scope that I can't hold in my head the relevant [meta] information about them for that usage, that's a problem for me, probably of quite a different kind.
This is a brilliant observation, thanks. Reminds me of Yegge’s observations about large codebases being like dirt, and the more dirt you have, the more machinery you need for pushing dirt around, and the more people, and the more systems for managing people and dirt-moving machines, ...
And the better way forward might simply be to have less dirt.
http://steve-yegge.blogspot.com/2007/12/codes-worst-enemy.ht...
Here's a very simple counterexample that I don't believe it's possible for a compiler to detect: writing a - b when you should have written b - a. More generally, that applies to any non-commutative function which takes two arguments of the same type.
This is also a good example of where a coding convention could help: if those variables had been named more descriptively it would have been much easier to see which way around they should go.
Of course, that would upset Joel on account of not knowing what - did without knowing the types.
Forcing a and b to be different types could cause a different problem - maybe a worse one, depending on how many subtractions your code requires.
In practice, I find it best to avoid non-commutative functions with two arguments of the same type. The obvious choice (for a DB I'm writing):
addRecord :: ByteString -> ByteString -> IO Bool
addRecord key value = do ...
But this is safer: addRecord :: DBKey -> DBValue -> IO Bool
Now `addRecord value key` won't compile.Coding conventions don't detect errors, they just help to make them more obvious. For instance, if the variables had been named "cake" and "slice", it probably would have been a bit more obvious which side of the '-' sign each one should be on.
In any case, it's not an either-or choice between coding conventions and tests. A sensible developer will have both.
> In practice, I find it best to avoid non-commutative functions with two arguments of the same type.
I guess there's a silent "..where possible" attached to that? :-) There are many times where it's not possible, or at least not sensible; and having too many type conversions in your code can be just as bad for readability as having too few.
I disagree. If the example has a better solution (and in some other comment here I argue it does, though it's not at compile time), it's well worth looking if that solution could be transferred to other problem domains that could be used as examples instead.
The deeper issue is that naming conventions are a kind of automatism, and humans shouldn't be bothered with that, they are simply not very good with this type of automatism. For repetitive patterns, automated solutions should be used.
Advocating these repetitive type prefixes reminds me of the classical Design Patterns -- they might be the best option if you're stuck with one set of tools, but IMHO it's important to take a broader view and see if there are maybe other tools that can alleviate the need of such patterns. And indeed, in the case of the classical Design Patterns, there are. More powerful languages render some of them obsolete, or turn them into trivial 3 line solutions that you can hardly call "patterns" anymore.
I agree, and this is what I am trying to get at with my comment: The deeper issue of whether naming conventions are a good way to make code better is much more interesting and important than the question of how to sanitize strings in a web app.
When there is no choice you must make do but amongst those with choice, why not look for better tools? Haskell monads have already been mentioned. I await easy to use dependent types and or statically verified contracts. But till then many powerful but relatively simple though different concepts can already be used today. These give static leverage over many semantic issues. Giving the act of building a program a flavour of the elegance in Euclid's Elements.
As noted, monads are useful and available in any with parametric polymorphism or dynamic types * . Haskell has Generalized Algebraic Datatypes. Pattern Matching Views is available in Haskell,ocaml,f#, scala. predicate dispatch is mostly there in clojure. People should be doing as much as possible to reduce errors and mechanize as much as possible.
*I'm aware monads become really powerful with higher kinds and that you give up some guarantees when you drop purity and even more when you go dynamic but it still remains a powerful and useful mechanism due to the body of theory backing it.
This is the most gorgeous description I have ever seen of this approach to programming.
Not that I've tried this approach myself, but I'd be curious to hear from anyone who has.
You can do this in any language today by boxing strings. In an “untyped” language like Ruby, you’ll get runtime errors if you try to use an UnsafeString where you expect a String, and in a statically typed language you’ll get a compiler error.
Some languages make this easier than others, but the main problem is not whether you can use types or boxes to solve the problem. The main problem is that if you are using libraries/frameworks that doesn’t support your types or boxes, you still have to be careful to place boxing and unboxing code where your code interacts with the library.
Taking a web framework as an example, if its template system expects PUS (plain unsafe strings) and it provides post parameters as PUS, it’s up to you to box things up so that your code never passes PUS straight through the system. I agree that once you box up a PUS it won’t get inserted into a template, but the pain point is that library authors don’t implement anything like this so you are going to have to deal with the rather broad interface between your code and almost every library that does something with a string.
Which is not to say that types are or are not better than coding conventions, but rather to say that in theory types are an airtight solution in any language, but in practice there is still broad scope for human error thanks to libraries.
And even in C you could wrap your (char*)s up in different structs. The typing overhead would be higher than in Haskell, though.
(There's even a language extension to allow you to overload string literals. But you probably don't want that, because
s = TaintedString "My string literal"
is easier to read than: s :: TaintedString
s = "My string literal"
The overloading makes more sense for implementation details like using a different String implementation like ByteStrings instead of a linked list of Characters. You actually want to be able to see the semantic differences.) s = "My string literal" :: TaintedString
?It's not shorter than the constructed version but it should work, you sometimes need it to get the "right" kind of number out of a literal when the type inference gets confused (or more commonly in the REPL)
Tom Moertel (http://blog.moertel.com/articles/2006/10/18/a-type-based-sol...) has.
As far as encoding semantics in to the type system, it would be great and help eliminate bugs, it wouldn't do a single thing to reduce the mental overhead out lined above. Basically they solve the same problem by two different paths, one makes doing the right thing easy, the other makes doing the wrong thing hard. Full semantic analysis probably isn't possible in a reasonable way, but catching the low hanging fruit should be possible.
I think there's a relationship between the number of people working on a codebase and how effective/burdensome coding conventions are. The more people on a project, the more friction there will be (at first) getting everyone to adhere to conventions. And this assumes there are code reviews in place.
I think this curve happens starting much lower than you'd think, too. Good hackers will be picky about conventions as soon as a codebase reaches ~4 hackers.
Thoughts?
I don't think so. I wouldn't have any problems working with GNU Style or Apps Hungarian if it was an interesting project.
One possible advantage that conventions have is that they can be easier to understand, and they can be transparently and painlessly broken when they don't make sense.
In other words, they're equivalent to a weak (ie, can have holes punched in it), informal type system.
Encoding a 'u' into variable names for "unsafe" strings and omitting it for ones that have been encoded safely for HTML bodies is nice. If this is the only problem to solve and you can universally apply this technique then it solves the "NUL terminated string of the 2010s", and I may use it but…
What about encoded safely for URLs?
What about encoded safely for use as a Unix file name?
What about encoded safely for SQL? (yes, don't do that, I know).
How about encoded safely for 7 bit ASCII only applications?
How about the output of base64? It isn't unsafe for HTML, but it is for URLs.
What about that bit of HTML that the user entered using their WYSIWYG field that needs to be sanitized but not encoded? 'u' seems right, but Encode(uVar) isn't the proper handling.
How about that library that doesn't know the convention, so its function calls all look like they are encoded safely for HTML since they don't start 'Us'?
What about the library that used 'u' for UTF-8?
(Oh, and…
dosomething()
cleanup()
… is broken. Requiring a caller of dosomething() to remember to cleanup() is about as polite as leaving set bear traps in your living room furniture and reminding guests to check under the cushions before they sit. Programmers are used to that sort of abuse (Remember to fclose() what you successfully fopen()), but the future should really provide use with better constructs, and it not being 1980 anymore, we are the future.)And of course, the 80s already provided better constructs[0][1]
[0] http://www.lispworks.com/documentation/HyperSpec/Body/s_unwi...
[1] http://www.gnu.org/software/smalltalk/manual-base/gst-base.h...
I personally believe that all code that is sensitive to escaping issues, should be escaped on output (or storage / sending to the database / whatever) by default unless you explicitly opt it out.
In our framework, we maintain 'already escaped' strings as a separate class, forcing the developer to acknowledge this fact. The HTML output layer escapes all strings unless they are instances of this safe class. Similarly for the SQL layer, all code gets quoted unless it was explicitly marked as 'safe'.
urlTheLink
filename
sqlSomeQuery
asciiText
b64DataBlob
...
You're allowed to use prefixes longer than just one letter.I'm not arguing for this convention (I don't much like it myself); but I don't think your argument is a very strong point against it.
EDIT: Oops, sorry, I just noticed that it wasn't you who brought up the explosion in the first place.
Now you have something which can still look clean (how clean may depend on your language, type inference, etc.) without bringing in messy pseudo-Hungarian-ness, which stops me doing the most important thing with my code - reading it easily.
Obviously it's only a silly and trivial example, but I'm not sure this article has dated that well in some regards (or perhaps only really applies to certain types of languages - Visual Basic, for example, pretty much forces you to do something like Hungarian to maintain any semblance of sanity long term).
Personally, I'd rather go Hungarian.
Edit - well, yes, you may well be able to do this sort of thing nice and easily in Haskell, but I'm not sure that going with Haskell to avoid the training and maintenance overhead of complicating your Java (or whatever) is a net reduction in workload and hiring difficulty...
type UnsafeUserGenerated = UserGenerated a
instance Monad UnsafeUserGenerated where
... (monadic boilerplate skipped)...
apiFunction :: String -> String
funcOfUnsafe :: U -> UnsafeString
funcOfUnsafe unsafe = (liftM apiFunction) unsafe newtype Unsafe a = Unsafe a
instance Functor Unsafe where
fmap f (Unsafe k) = Unsafe . f $ k
addTwo :: Int -> Int
addTwo = (+2)
unsafeAddTwo :: Unsafe Int -> Unsafe Int
unsafeAddTwo = fmap addTwo
But really, I'm not sure this is the right approach. Even values generated inside my program need to be quoted for inclusion on an HTML page. What you want to avoid is double-quoting, so what you need is simpler: data Content = Quoted String | Unquoted String
output :: [Content] -> Content
output = concatMap f
where f (Unquoted x) = quote x
f (Quoted x) = x
Now the type system ensures that (output . output) == output, which is what you really want to ensure. Tainted data, I think, is a separate concern. And the solution, in that case, doesn't involve a functor, it involves making sure your library tags everything as Unsafe and that your data validation functions remove that annotation: type Params a = Map String (Unsafe a) -- keys may also be unsafe, YMMV
readHtmlForm :: Request -> Params String
validateField :: Validatable a => Unsafe a -> a
main = output . Unquoted . validateField . get "foo" . readHtmlForm <$> fakeHttpRequestFunctor vs. Monad in this case is a way of talking about how exactly this construct should work --- it's not a meaningless distinction. As another response to my comment mentioned, both a functor or a monad are applicable. The question is whether two instances of this data type (I'm using non-Haskell-y terms for clarity) have influence on each other (in rough terms). So it's not that I was complaining that the parent was wrong. I was complaining that the semantics he was imposing on his quoted strings were too restrictive. Another poster instead mentioned ways in which mine were too loose. So my post was part of a constructive debate on what exactly we want quoted strings to do. That Haskell provides a vocabulary for communicating precisely and tersely is not it's fault, it is one of its strengths.
Or perhaps an arrow or a co-monad are better? ;o)
Yes. But that's a feature, not a bug.
http://perldoc.perl.org/perlsec.html#SECURITY-MECHANISMS-AND...
I gave up on perl's taint mode when I discovered this bug http://bugs.debian.org/411786 , in which perl randomly sets the taint flag due to a utf8 bug.
Anyway, I'm not sure why it would be less cool even if it were inspired by another language (which I'm sure it was; Perl, like English, elevates borrowing to an art form).
Considering how dynamic perl is, and that you can mix tainted and non-tainted values in a single collection (for instance), I don't see how a perl program could be statically analyzed for taintedness misuses.
The reality is that nobody uses taint mode, though, for whatever reason. If you look at my comment up the page, the problem that people have is not managing the safety of data, it's making sure that they present the right "view" of that data to the right component. HTML needs to be escaped, but not if it's already been escaped, and so on.
Uh? I do use it; it's extremely efficient. I'm probably not the only one :)
Right, so it's not at compile time. Thank you.
For example, I do a lot of Excel+VBA programming. In VBA, the native data type is `Variant`. And if you pass something from Excel to a VBA function, that's what you get. But you also know that legal Excel values are just a subset of what VBA allows in a `Variant`. So I find it very useful to stick a clue to myself in the variable name, so say, `f(parm)` becomes `f(xParm)`.
I have libraries that work with the `x` subset of `Variant` (and subsets of that - i.e. `xs` denotes a simple value that can go in a single Excel cell). The prefixes provide useful clues that the language can't reasonably help me with.
I have done a bunch of mathematical programming in java, and you end up writing ".add()" a lot. Maybe .add is on a finite field. Maybe it is some matrices. Maybe complex numbers. I don't see what I am saving over operator overloading.
Of course, if I used .addFiniteField, .addMatrix, .addComplex, then I can see the (possible) benefit, of knowing how "heavy" the operation is at a glance. However, I don't think it is worth the cost, given I lose genericness.
I think the usual complaint is that people use operator overloading for non-mathematical operations. For example, a guy doing a webapp might overload the "+" operator for Group and User as a clever way to add a User to a Group. This turns out to be a terrible idea.
In fact, my eyes glaze over as I read Scala code for this very reason, because everyone and their mother defines a method with some random array of symbols because it makes sense to them and they prefer shorthand. It makes the code totally unreadable.
Good abstractions are good, but operator overloading combined with templates and implicit casting is not one of them.
People always talk about it what can happen, but I've never seen instances of operator overloading in C++ that I objected to in a fundamental way.
Also, please, take a look at the boost::lambda or other boost libraries -- they have large DSLs written on top of operator overloading which look nice when you look at them, but are terribly complicated to use and ridiculously hard to debug, thanks to the completely unintelligible messages that the compilers produce.
Most probable reason you've never seen it, is that most sane, experienced people know the danger and avoid it. When there's more than one person working on a project, it's way more important for the code to be easy to follow and debug than to look fancy.
You are correct in that Spirit is difficult to debug, but that has nothing to do with operator overloading. That has to do with its extensive use of template metaprogramming and C++'s lack of concepts (http://en.wikipedia.org/wiki/Concepts_(C%2B%2B)). If they switched the whole library over to using named member functions, the debugging problems would still be there. And concepts would go a long way to preventing the compiler problems.
I've looked at boost::lambda, and decided it was mostly a novelty. How far towards a real lambada could you get without lambda support in the compiler? That far, it turns out, which was not far enough. The rules I had to remember to use it in non-trivial situations was more effort than it required to write a one-off function object. While I agree boost::lambda is more trouble than it's worth, I haven't seen or heard of terrible things happening because people used it.
I also think it's worth noting that many Boost libraries are purposefully on the edge of what is possible in C++. It's like a refereed sandbox for experimenting.
I've also had no problems with << and >> as the stream read and write operators. But, I also have no experience with internationalization. I don't see any obvious problems, though, what are they?
It's possible I've seen about the same amount of C++ as you (perhaps in some different areas) and come to a different conclusion.
And this is precisely my point: nothing bad could happen because people haven't used it in serious projects, even though it is possible -- it's just more trouble than it's worth. This is actually the whole point of argument against operator overloading: it is troublesome for both implementors and users, and the value provided is minor - most people use operator overloading for math-like types (bignums, complex numbers, matrices) anyway. On the other hand, it requires (sometimes considerably) more work on the designers' and implementors' side, and more caution and experience on the programmer's side.
That said, I'm not against operator overloading myself. However, I'm heartily against operator overloading in C++, because it interacts really, really bad with other C++ features (especially implicit casting rules (which are bad anyway) and manual memory management). Please, see [1] for more information, it's explained better and wider there than I could do it here.
Regarding bit-shifting I/O, I've never figured out why they even thought it's a good idea at all. It isn't any more terse. If it's combined with output formatting it's much harder to figure out what's actually going to be written than with printf-like functions, because the formatting and expressions are mixed together. The only advantage I can think of is that it can be a little faster than parsing the format string every time. But still, you don't need to parse it every time. If the language design allowed it, they could have introduced something like CL's define-compiler-macro, but I think the utter mess created by presence of such tool in C++ would be overwhelming -- it's bad enough as it is, with template "metaprogramming" instead of real metaprogramming facilities.
The internationalization issues I mentioned make this completely useless: the usual way to support multiple interface languages is to use something like gettext for strings in code (see e.g. Qt Linguist for C++ implementation). Basically, for every string that's supposed to be user visible, you use a special translating function, i.e. instead of
printf("Hello, %s, it's %s!\n", name, day_of_week);
you write
printf(tr("Hello, %s, it's %s!\n"), name, day_of_week);
This simple approach does not map at all to the bit-shift I/O -- I'm fairly sure that experienced C++ hackers could create something along the lines of
cout << (translate << "Hello, " << name << ", it's " << day_of_week << "!\n");
but this is obviously useless because obviously not all (not even many) natural languages has syntactical structure of English. Better option would be:
cout << (translate("Hello, %{1}, it's %{2}!\n" << name << day_of_week);
but again, it's clearly only printf with unnecessarily convoluted syntax.
C++ libraries which do abuse operator overloading (see the boost::spirit parser library as the best example) have this problem. For example, spirit wants to define the standard '' and '+' parsing operators. However 'a+' and 'a' aren't valid C++, while '+a' (unary +) and '*a' (dereference) are, so those are used.
The Spirit code is unlikely to be mixed in with other code. Or, put another way, I was never bothered by your complaint, because I always read it as EBNF notation first.
My real problem with Spirit was performance. It took three minutes to compile a 7000 line C++ program - more with optimizations turned on. The binary with debugging information turned on was 24 MB. And the enormous amount of copying it performed at runtime meant parsing 300 line files took a few seconds. (Luckily the runtime performance of my compiler was never important.) The new version may have fixed these problems.
You can make up your own operators. That's not always readable, but at least if you see some funny symbols, you know that you have to look it up.
With Haskell's typeclasses (and I guess Ocaml's Functors should allow the same) you can have the functions of the same name that work differently for different types. E.g. there's a Show-typeclass, whose instances have to provide a `show :: a -> String' function. The Num-typeclass includes operators such as `(+) :: a -> a -> a', which means that + always takes to arguments of the same type and returns the same. So you wouldn't be able to overload + to add a user to a group.
You might come across somebody writing
(+!) :: User -> Group -> IO ()
for adding a user to a group (in an un-idiomatic way). But at least there's a funny symbol to alert you, instead of a re-used normal one.If you need to pass in something that doesn't need to be escaped, you switch off the escaping explicitly (`<% var name ESCAPE=NONE %>`), and/or you put HTML strings into a separate type which doesn't get escaped upon substitution.
For example the template engine would return a `HTMLString` instead of a `String` object, so that it never accidentally encodes twice (as could happen in the case of includes).
For example sometimes you get a piece of HTML that you have to include verbatimely for policital reasons (ad banners come to mind that need to be included character by character because of the Terms of Service - see AdSense for example).
In that case you really need an option for direct, unescaped HTML inclusion.
Or you might need to hilight the a syntax that's hard to parse, and the only available hilighter spits out HTML pieces instead of parse trees.
My point is that a template system should be able to deal with such realities, and it doesn't make it "fundamentally broken".
So you are working in a language which don't allow you to defined custom types. You can work with built-in types like strings and integer, but if you want to add "metadata" to these basic values, you can't do it in the type system. Hungarian notation is a perfectly legitimate solution given those constraints.
The distinction between "Systems" and "Apps" Hungarian also makes sense in this context. Systems Hungarian encodes the name of the built-in types. This made sense in BCPL (where it originated), but is cargo cult in VBScript, where this is already expressed in the type system. Apps Hungarian allows you to express "custom types" which is not expressible in the type system of VBScript and therefore makes sense. But it is cargo-cult programming in any language which allows you to create custom types or objects. Which is basically any other modern language.
(The thing about exceptions being "invisible gotos" also makes sense in the context of VBScript, where exception handling was done through statements like "On Error Goto..." and "On Error Goto Next".)
So for Java, you might have a class with the String stored as a private variable and the obvious getter/setter methods. In Haskell, you'd have a newtype with similar functions. (Can this be done in C? I don't know.)
A close approximation to Joel's example (string sanitation) in Haskell with newtypes: http://blog.moertel.com/articles/2006/10/18/a-type-based-sol...
That way, I know that single-quoted strings, which is most of them, won't ever involve user input, while double quoted strings get a little extra scrutiny. It's not XSS protection by itself, but it's a useful little way to increase the odor of the code one way or another.