Post #5,434
8/16/01 1:17:28 PM
8/17/01 4:39:46 PM
|
I've decided to write it all down
My philosophies and opinions on software development/architecture. I've kicked off a weblog at [link|http://ventedspleen.weblogger.com|http://ventedspleen.weblogger.com] (link is now fixed) to try to nail down just why I have the opinions I do. At the moment I'm still focusing on languages but I expect to move beyond that at some point.
Anybody wanting to contribute or comment is most welcome.
|
Post #5,439
8/16/01 1:40:36 PM
|
I get an error
When I try to access [link|http://ventedspleen.weblogs.com|[link|http://ventedspleen.weblogs.com|http://ventedspleen.weblogs.com]]. Is there a specific page in the URL?
|
Post #5,453
8/16/01 3:27:54 PM
|
Doh! This one
[link|http://ventedspleen.weblogger.com/|[link|http://ventedspleen.weblogger.com/|http://ventedspleen.weblogger.com/]]
|
Post #5,469
8/16/01 4:38:54 PM
|
Pet peeves
Operator overloading in C++.
Idiots writing C++ code that doesn't properly take care of memory (you know, blah = new blahclass without delete blah)
Undocumented sources - not the source itself (although people who write code without a single comment are hurting themselves and others), but where the code came from, if anywhere. Combine that with making your own code unattributable by not providing a minimal header.
Re-inventing the wheel, when there are several types of wheels out there to pick from. OK, OK, it isn't always possible to reuse code and sometimes actually harmful, but it would be just plain stupid to write a string class from scratch in C++. Even if you don't want the templatized standard library string, there are many implementations to choose from.
Most of these are C++ examples, because I've been working on a library that exhibits many of these features.
The first time I encountered setjmp() was in an Amiga program ported from Unix. "Hmm, what's setjmp()?" I said, pulling up the man page. I read the man page. "*GASP* GLARGGGPPPHHTT!!! ARGHJKLKJ#@%!^&^U!" I exclaimed, and rolled my chair over backwards as I fainted.
|
Post #5,471
8/16/01 5:09:43 PM
|
Well, if you just want to list those
I'll wrap the whole thing up in "People who use C++ to write application code".
Done.
setjump is a perfectly good routine. Its used for exception handling in Objective C and it works OK. It just happens to break what C++ crowd is pleased to call their "runtime architecture".
|
Post #5,473
8/16/01 5:18:02 PM
|
I don't care what the C++ crowd calls it
Setjmp is a miserable solution to a built-in language problem - this is from a C perspective. C programs also use it for exception handling, though probably much less coherently than Objective C. I don't think I've ever seen a C++ program use it.
The first time I encountered setjmp() was in an Amiga program ported from Unix. "Hmm, what's setjmp()?" I said, pulling up the man page. I read the man page. "*GASP* GLARGGGPPPHHTT!!! ARGHJKLKJ#@%!^&^U!" I exclaimed, and rolled my chair over backwards as I fainted.
|
Post #5,659
8/17/01 3:10:16 PM
|
Argh!
Operator overloading in C++ is one of the few things about it that's any good. It's very useful in numerical work.
|
Post #5,672
8/17/01 4:37:23 PM
|
Well hang in there
because its coming to Java I think. Although, if they make the same compromises they made with the Generic extensions (templates), its going to be horribly inefficient for numerics work.
|
Post #5,716
8/17/01 10:31:56 PM
|
Two uses
Complex numbers and strings.
Can't think of any other reasonable uses. To have a language specification that builds an item on two rare uses is crazy.
French Zombies are zapping me with lasers!
|
Post #5,490
8/16/01 7:37:39 PM
|
"Boy, what a lot of typing that was!"
I find that comment about a Java fragment to be off target. What is more important is how easy it is to read. A program with any longevity will be read much more often than it is written or modified. For another thing, it seems to discourage comments. After all, they take time and space and typing as well.
The question to answer is "How clear is that?".
Alex
Only two things are certain: the universe and human stupidity; and I'm not certain about the universe. -- Albert Einstein (1879-1955)
|
Post #5,549
8/17/01 12:27:05 AM
|
Re: "Boy, what a lot of typing that was!"
> For another thing, it seems to discourage comments.
The problem is meaningless comments, or worthless comments. Basic coments saying the purpose of the routine/function/subroutine/class seem to be a lost art.. That's probably true in the smalltalk/Objective C/Common Lisp world as well as in C/C++.
French Zombies are zapping me with lasers!
|
Post #5,654
8/17/01 2:38:37 PM
|
Thats a good point
I think I may go back and change that - because the clarity thing is definitely a big deal.
|
Post #5,493
8/16/01 8:09:16 PM
|
Nice rants.
I like the off-by-1 discussion. It reminds me of why I now avoid C if I can. But to see Java as no better... !
Icon gets closer to Smalltalk in that regard. To loop over all items in a colletion you do every item := !collection do { ... } Hey presto: no off-by-1 errors! The other Smalltalk examples aren't as elegant in Icon as they are in Smalltalk - but that's okay. Icon is in the gap between C and Smalltalk.
Wade, who was deep in Icon programming last night and was quite enjoying it.
"All around me are nothing but fakes Come with me on the biggest fake of all!"
|
Post #5,591
8/17/01 9:01:42 AM
|
Re: I've decided to write it all down
Not all statically typed languages share the defects you observe in C++ and Java. For example, in OCaml a) there are no type declarations -- all types are inferred, b) there is no null belonging to all types (since this makes the type system unsound), and c) there are no casts, since the type system is expressive enough to make them unnecessary. For instance, consider the following two class declarations in OCaml: class oned_point xarg = object val mutable x = xarg \t method get_x = x method set_x d = x <- x + d end
class twod_point xarg yarg = object val mutable x = xarg val mutable y = yarg \t method get_x = x method set_x d = x <- x + d method get_y = y method set_y d = y <- y + d end
They should be pretty easy to read. The only novelty is the lack of explicit type declarations in the method, variable and constructor arguments. Now, consider a pair of functions let distance_from_x_axis obj = abs(obj#get_x) inferred type: < get_x : float; .. > -> float
let manhattan obj = abs(obj#get_x) + abs(obj#get_y) inferred type: < get_x : int; get_y : int; .. > -> int = <fun>
The '#' notation is the same as the dot-notation in Java or C++ -- it denotes a message send. Notice that the type inference restricts the argument of distance_from_x_axis to objects with a get_x method. This is statically verified, but any object that has it is acceptable. At the interactive prompt: # let o1 = new oned_point 9;; val o1 : oned_point = <obj>
# let o2 = new twod_point 3 9;; val o2 : twod_point = <obj>
# distance_from_x_axis o1;; - : int = 9
# distance_from_x_axis o2;; - : int = 3
# manhattan o2;; - : int = 12
# manhattan o1;; Characters 10-12: This expression has type oned_point = < get_x : int; set_x : int -> unit > but is here used with type < get_x : int; get_y : int; .. > Only the second object type has a method get_y
The error in the call 'manhattan o1' is a compile time error -- the expression was never executed. This doesn't change the force of your argument much, though. If anything, it makes it more damning since there were superior type systems that Java ignored. :)
|
Post #5,632
8/17/01 12:56:13 PM
|
I've read the previously posted rant
on why ocaml is an example of good static typing. I believe it to some extent, but I find the ocaml syntax incomprehensible. I think I need it broken down better as I lack the foundation to fully understand your example. For instance, can you explain to me exactly what the parts of this statement:
method set_x d = x <- x + d
mean? I get that its a method declaration for a method called set_x. I'm not quite sure what d is and I don't understand at all what d = x <- x + d is supposed to mean. It looks to me more like moveBy d than a moveTo d sort of thing with the + there.
Also, how does the language handle type widening or narrowing when the compiler can't get complete knowledge of all sections of code? For instance, in the case of dynamically loaded libraries or frameworks. Or is this just not possible?
|
Post #5,650
8/17/01 2:34:21 PM
8/17/01 2:35:05 PM
|
Just a guess
method set_x d = x <- x + d
I think the equivalent Python would be
def set_x (d): x = x + d
Jay O'Connor
"Going places unmapped to do things unplanned to people unsuspecting"
|
Post #6,136
8/21/01 4:27:23 PM
|
Then its misnamed
if it adds d to x then its not setting x, its a moveXBy d.
Thats a little confusing to me so I wanted clarification.
|
Post #6,280
8/22/01 10:03:37 AM
|
Re: Then its misnamed
Okay, I tried to post this a couple of days ago, but it didn't seem to work. Here's the five-minute Caml lesson. First, function definition: function bindings are introduced with a 'let' construct: let add a b c = a + b + c
To call a function, you do this: add 3 5 7 - : int = 15
Note that there are no parentheses -- simple juxtaposition will do. Like all functional languages, Caml supports nested and first-class functions, and uses tail-recursion to do looping. let fib n = let rec loop i a b = if i = 0 then a else loop (i-1) b (a+b) in loop n 1 1
fib 6 - : int = 13
Now, since Caml is functional, by default variables are immutable, and can't be mutated. As a result, if you want a mutable variable, you need to use a reference: let x = ref 3 val x : int ref = {contents=3}
!x (* Dereference x *) - : int = 3
x := !x + 3 (* Increment the value inside x *)
!x - : int = 6
Now we come to one of the annoying redundancies in Ocaml syntax. The ':=' notation is not the only way to write mutation. When you have records with mutable fields, then you can use the '<-' notation for mutation as well: type foo = {mutable bar: int}
let x = {bar = 6}
x.bar - : int = 6
x.bar <- x.bar - 3 (* The record field is mutated *)
x.bar - : int = 3
Now, the quick and dirty object tutorial. Here's a simple class class foo = object val mutable x = 0 (* A mutable instance variable *)
method get_x = x (* A getter for the instance var x *) end
let obj = new foo
obj#get_x (* Method call uses '#' instead of '.' *)
You can add arguments to the initalizer like so: class point u v = object val mutable x = u val mutable y = v method get_x = x method get_y = y
method move dx dy = begin x <- x + dx; y <- y + dy; end end
let pt = new point 3 5
pt#get_x - : int = 3
pt#move 4 5
(pt#get_x, pt#get_y) - : int * int = 7, 10
|
Post #5,745
8/18/01 9:54:21 AM
|
Posted a link...
...over on [link|http://lambda.weblogs.com/|Lambda weblogs]. One nice thing about Winer's discussion boards is that it allows you to keep track of who's [link|http://ventedspleen.weblogger.com/stats/referers|referring] to your board (Wonder if zIWETHEY tracks such things? - though I can see how some folks dislike such tracking mechanisms).
My only thought at the moment concerns the Open-Closed principle and the use of delegates/posing. What I am wondering is how you view the ability to test software when it's behavior can be modified? The main reason for the closed principle, as I see it, has to do with the knowledge that the software has been tested and you have confidence in it's algorithms. Once you start allowing a class to behave differently depending on the programmer/application, it becomes more difficult to nail down when a class is debugged and when it is not.
|
Post #6,075
8/21/01 12:32:25 PM
|
Thanks
Its more fun when you get readers and besides, some of the feedback I've been getting here and elsewhere has made me either rethink and sometimes refine my positions.
On the Open-Closed Principle. In the whole, I don't think its an awful idea exactly. The idea being is you don't have to re-test what you didn't change. The flip side of this is that you *do* have to test anything you do change or anything that's new.
The assumption that is often made with these principles is that the original design is sound and a good fit for the problem domain. What about systems that need to evolve in environments where the current design doesn't fit the (recently changed) requirements very well?
In my rather advanced paranoia, I find myself factoring at a rather fine grained level to leave myself options in places where I'm not sure I'll need them. I consider this to be the essence of the architect's role - the preservation of options. After several years, my instincts have gotten pretty good, but I still get surprised every now and then.
Plus - how often have you had a system dumped into your lap that isn't in too good a shape and you still need to rev it to do some new kind of thing?
So I guess to sum up - the literature all assumes blank slate design or extensions of systems where the initial assumptions still hold. Thats all well and good - the real world isn't that neat. Our tools should be forgiving and help us recover from bad designs or sweeping changes in requirements. That would be the test of a good vs bad system for development of enterprise systems.
|