First class developer: who ?

M

Martin Gregorie

often, if there is no good way to test or use a piece of code, well then
this is a bit of an issue...
If its not testable, how do you find out if it does *anything* correctly?

IMO, an essential part of the design is making it testable. That should
have equal priority with usability, adequate error and incident reporting
and a way to measure performance. All are essential to developing a good
application. I seldom use a debugger, preferring to use the trace log
approach to debugging & testing. I make sure the tracing and its detail
levels are easily controlled and invariably leave this code in the
product. The performance hit is almost undetectable when its turned off
and the payoff from being able to turn it on during a production run is
huge.
 
B

BGB / cr88192

Martin Gregorie said:
If its not testable, how do you find out if it does *anything* correctly?

yep.
that is the issue...

this comes up in cases where the data needed to test the code can't be
easily generated or simulated, which means it could only be adequately
tested once the other code which provides its data exists.

often, I have noted that in these situations, the code to produce such data
would itself be non-trivial, ...


so, as noted, it is a bit of an issue.

the end result is that one often finds that the end result of such an effort
is often not terribly useful anyways...
as an odd feature of reality, most things which tend to be useful also tend
to be easy to test.


this is also a big part of why I stay away from "AI" and similar fields, as
it is a large space where it is neither clear how to implement, much less
how to test, most of this stuff...

IMO, an essential part of the design is making it testable. That should
have equal priority with usability, adequate error and incident reporting
and a way to measure performance. All are essential to developing a good
application. I seldom use a debugger, preferring to use the trace log
approach to debugging & testing. I make sure the tracing and its detail
levels are easily controlled and invariably leave this code in the
product. The performance hit is almost undetectable when its turned off
and the payoff from being able to turn it on during a production run is
huge.

I use a debugger some amount, usually to identify where a problem has
occured and the situation at the place it has occured.
 
B

BGB / cr88192

Patricia Shanahan said:
BGB / cr88192 wrote:
...

The conditions that trigger a piece of code in operation may be
difficult to cause in system test, but the code itself can be unit tested.

I find it helps to write unit tests and detailed specification, usually
Javadoc comments, in parallel. Sometimes a small change in an interface
that does not harm code I'm going to write using it makes the
implementing classes much easier to test.


well, I am thinking in language-neutral terms here (because I write code in
several languages).

for example, my compiler/VM project currently involves ASM, C, C++, and
Java, all in use at the same time (and, this set may extend). (this is, in
addition to several smaller/special-purpose mini-languages).


a lot also depends on the size and complexity of the "unit" to be tested...


the main issue is with larger pieces of code (say, 50-100 kloc or more)
which need in general difficult-to-produce test data.

an example of this would be a compiler-codegen (converting from some IL into
ASM, for example), which can prove difficult to test on its own.

in this case, one will need typically a compiler frontend, as well as some
amount of code to be run through it, to produce the IL to effectively test
the codegen.

even then, one may end up spending years finding and fixing all of the
little bugs which either escaped notice before or which got introduced as a
result of later activity, ...


sometimes, may also end up with code which is subject to "system effects",
where many of the parts will play together in a way which is unexpected, and
so one has to test that the entire system works well (rather than being able
to test all the individual parts and determine that "the whole" will work by
extension).

or, in cases where a minor issue in one place will go and cause something to
misbehave elsewhere, ...

these particular sorts of situations can prove difficult to unit-test...


however, a partial answer I have used here, and I tend to stick to, is to
try to split things into "layers", where each layer produces data which can
be manually examined, and where often the input for each layer can be
written by hand if needed.

for example, my assembler accepts textual ASM, my codegen accepts textual IL
and produces textual output (ASM), internal signatures, serialized metadata,
.... is also textual.

I have ended up preferring textual data as it makes things much easier to
examine and write by hand, even in many cases where it is not necessarily
optimal (vs a binary or direct-API-call interface).

however, a lot of these notations also tend to be character-based, rather
than token-based.

I will not claim originality here, and in fact the notation for my
type-signature system, which is one of the more notable examples of this
usage, had been influenced some by the signatures used within the JVM, but,
also, a lot by the IA64 C++ ABI, which is used by GCC and friends, and as
well has a few things in common with MS technologies (although MS uses
binary signatures and a different metadata layout).

....


however, there are many other things which are much harder to test, but I
have also found that things which are particularly hard to test are also
often similarly difficult to find a use for.

more often, anymore, I will write down the idea for things like this, and
implement them if/when a need to them may appear (which will often at the
same time provide the test-cases).


or such...
 
M

Martin Gregorie

I use a debugger some amount, usually to identify where a problem has
occured and the situation at the place it has occured.
I've usually found debuggers to be the slower option in terms of
programmer time. Tracing statements that show data as well as location
are a better way of tracking down the problem - especially when the
causative code is some distance from the apparent trouble spot.
 
T

Tom Anderson

BGB / cr88192 wrote:
...

The conditions that trigger a piece of code in operation may be
difficult to cause in system test,

If it's difficult to cause with a system test, then it's possible, and
that's something that can and should be tested - corner cases are
something you should pay particular attention to, because they usually
won't be caught by informal testing.

If it's impossible to cause with a system test, then why is the code
there?

At work, for various bad reasons, we write only system tests. We do
test-first development, and have almost all the system's code covered by
tests. The only bits that aren't covered are where deficiencies in our
test tools stop us getting at them (eg something on a web interface that
involves very complex javascript that's out of our control, where our
ancient version of HtmlUnit can't handle it). I've yet to write anything
that could not in principle be tested by a system test.

Hence, i do wonder if the orthodoxy that unit tests should be the
foundation of developer testing is right. Their advantage is that when
they fail, they give you a much more precise indication of what's gone
wrong. Their disadvantage is that they don't test interactions between
components (or if they do, they become less unitish, they lose some of the
aforementioned precision), and don't provide the lovely whole-system
reassurance that system tests do, the kind of reassurance that means you
can point your product owner at the Hudson chart and say "look, it all
works!".

I appreciate that TDD orthodoxy says that you should also have functional
system-level tests, to provide that kind of verification. But if you have
those at a comprehensive level of density, why do you also need unit
tests?
but the code itself can be unit tested.

I find it helps to write unit tests and detailed specification, usually
Javadoc comments, in parallel. Sometimes a small change in an interface
that does not harm code I'm going to write using it makes the
implementing classes much easier to test.

I think you're saying you write the tests and the spec at the same time
rather than writing the spec first. I come from the opposite direction -
we only write javadoc on interfaces we're exposing to some third party (eg
in a library we're releasing), and then we only write it right before
releasing it. It's very much a case of the spec being a description of
what we've built, rather than a true specification. We adhere to Extreme
orthodoxy in considering the tests the true spec. Er, even though we write
system rather than unit tests.

Actually, in the case i'm thinking of, the library has no direct tests.
Rather, we have a simple application built on top of the library,
essentially the thinnest possible wrapper round its functionality, and
then we have system tests for that application. So the application is sort
of simultaneously a test framework, a reference application and a living
specification. This may seem like a wildly marginal way to do testing and
documentation, and it probably is, but it seems to work okay. It really
forces us to test the library in practical, functional terms - it makes us
expose functionality in a way that actually makes sense to an application
developer.

Anyway, what i am working my way round to is an observation that for us,
writing documentation earlier, as you do, would probably be a good thing.
Back when i was a scientist, i would toil away in the stygian darkness of
the microscope room for months on end, doing what i thought was solid
work, and then invariably find that when i sat down to prepare a
presentation, poster, paper, thesis chapter, etc, that my data was shot
full of holes - controls i didn't do, possibilities i didn't investigate,
variables i didn't measure, related work i hadn't taken into account, and
so on. The act of bringing the data together in writing forced out all the
flaws, and gave me a stack of (mostly small) things to go back to the
bench and do.

Recently, as i've been javadocking some interfaces, i've found something
similar - we have something that works, and that makes sense to us
(including when writing the reference app), but when you have to explain,
say, what that parameter does in an @param line, you suddenly realise that
it's really being used to mean two quite different things, and should
really be two parameters, or that it should be a different type, on
another method, a field, or whatever. The act of explaining it to someone
- even the imaginary audience of the javadoc - makes you think about it in
a deeper way, or a different way at least. A way where you have to explain
what it does without being able to talk about how it does it. Interface
not implementation, with a bit of the Cardboard Programmer effect thrown
in.

So, er, yeah. There you have it. Rock on Dr S.

tom
 
B

BGB / cr88192

Martin Gregorie said:
I've usually found debuggers to be the slower option in terms of
programmer time. Tracing statements that show data as well as location
are a better way of tracking down the problem - especially when the
causative code is some distance from the apparent trouble spot.

the debugger will often blow up somewhere, and often one can look at the
state where it blew up and try to figure out what has gone wrong.

it is all much better than trying to figure out things, say, by dumping
logging information to a file and trying to make sense of this (although,
logfiles are often a fairly helpful means of detecting all sorts of issues
as well, such as writing out log messages whenever something looks amiss,
....).
 
M

Martin Gregorie

Martin Gregorie wrote:
...

It depends on how much is already known about the problem, and how long
it takes to run to a detectably erroneous state. If it takes a few
minutes, or I have a specific hypothesis I want to test, I like tracing
statements.
The other benefit comes from leaving all the tracing statements in
production code and making its controls accessible. This was a real get
out of jail free card in a broadcast music planning system I worked on a
while back. The data and how it was used mandated a complex, menu driven
UI and the users were a very bright lot, so the system was designed to
provide interactive access to trace controls. If the users rang with a
query, we'd just tell them to turn tracing on, do it again and turn
tracing off. This found all manner of stuff from finger trouble and odd
corner cases to actual bugs with the benefit of being able to talk the
user through it with the trace report and work out the solution there and
then.

The other thing we did (which I've never done since) was that we could
set up the database to stamp each changed record with the program ID and
timestamp. Again, that found an obscure bug on one of the overnight batch
processes that would have been hell on wheels to find without this
annotation. Needless to say, the culprit was not the program that crashed!
If I have no idea what is going on, and it takes four hours
to reproduce the bug, I want to squeeze as much data as possible out of
each failure. With a debugger, I can often find the answer to a question
I did not think of until after I saw the answers to other questions.

On that mainframe (ICL 2900 running VME/B) you didn't need an interactive
debugger. Coredumps were great - all data formatted correctly and
labelled with variable names, the failed statement and path to it shown
with source file names and line numbers - and did it without reference to
the source files. I wish current systems offered something like that, or
at least a dump analysis program that could do the same job.
 
A

Arne Vajhøj

the aspects of math used in computers, however, is very different from what
one typically runs into in math classes...

"subtle" connections are not usually of much particular relevance, as
usually it is the overt details which matter in this case. in many respects,
comp-sci and math differ in terms of many of these overt details.

Software development, computer science and math is not identical.

But they are strongly related and build on each other.
and neither are particularly math, FWIW...

They require math to understand.
this is about like arguing that someone has to understand convergence and
divergence to make use of things like the taylor series.

the taylor series works regardless of whether or not one understands
convergence...

That is where I disagree.

Learning that X solves Y without understanding why will with
almost certainty lead to using X to solve Z where it is bad
solution.
it is also far more useful to note that, for example, compound interest can
be implemented with a for loop, than to note that it can be modeled with an
exponential function.

I am a lot more confident in a financial calculation program
developed by someone that understand the formulas, then by
someone with elementary school math and a long experience with
for loops.

Arne
 
A

Arne Vajhøj

Arne said:
Arne Vajhøj wrote:
On 12-03-2010 05:26, Arved Sandstrom wrote:
Both terms actually have clear English meanings - "equality" means (or
should mean) that two things *are* the same, and "equivalence"
means (or
should mean) that two things can be substituted, that they behave the
same way.

A mathematical and CS definition in fact tracks the common English
meanings of these 2 words, and the language concerning Object.equals
that Patricia quoted does say exactly this: equals is an
implementation
of equivalence.

My point is that the common English, the mathematical and the CS
definitions of the two terms are pretty much the same. And programming
languages that differentiate between the two also track those
definitions. We see that Java does.

I don't think Java follow those rules.

I assume that you consider equals to be equivalence and
== to be equality.

But it is not so.

Example:

public class ZZ {
public static void main(String[] args) {
int iv = 2;
double xv = 2.0;
System.out.println(iv == xv);
}
}

I do consider == to be equality (identity). Same object for references,
same value for primitives...that's equality.

But 2 and 2.0 are not identity equals.

No, but as I indicate I don't see that we ever compared 2 and 2.0 for
identity in your example. You can't do that with direct use of ==,
because of promotion.

This is one of the C-based hiccups of Java. For primitives of different
types we shouldn't even be asking the question ==?
That promotion is done by ==.

"Done by?" I don't see that. "Done on behalf of", sure. Unless a
bytecode guru tells me otherwise I expect that == just like + is
blissfully unaware that it could ever be asked to work with two
primitives of differing types.

Now, if each binary operator duplicates all the numeric promotion logic
I could see that they are responsible for it.

The expression is iv == xv. Everything in that expression is about ==.

The promotion is part of what the Java language describe for
that operator.

== is not testing for identity equals between the two operands for
simple types.

The fact that it is doing testing for identity equals as part
of what it does does not matter because it is not doing it
on the two operands.

Arne
 
A

Arne Vajhøj

I've usually found debuggers to be the slower option in terms of
programmer time. Tracing statements that show data as well as location
are a better way of tracking down the problem - especially when the
causative code is some distance from the apparent trouble spot.

Not to mention that quite often the problem does not happen
when debugging. Concurrency problems are frequent problems.
Often debugging requires optimization turned off and then
bad code may work. Etc..

In server programming debuggers are mostly a tool to
impress pointy haired bosses with.

They are OK for many desktop apps.

Arne
 
A

Arne Vajhøj

I've usually found debuggers to be the slower option in terms of
programmer time. Tracing statements that show data as well as location
are a better way of tracking down the problem - especially when the
causative code is some distance from the apparent trouble spot.

Not to mention that quite often the problem does not happen
when debugging. Concurrency problems are frequent problems.
Often debugging requires optimization turned off and then
bad code may work. Etc..

In server programming debuggers are mostly a tool to
impress pointy haired bosses with.

They are OK for many desktop apps.

Arne
 
A

Arved Sandstrom

Arne said:
Arne said:
On 12-03-2010 21:08, Arved Sandstrom wrote:
Arne Vajhøj wrote:
On 12-03-2010 05:26, Arved Sandstrom wrote:
Both terms actually have clear English meanings - "equality" means
(or
should mean) that two things *are* the same, and "equivalence"
means (or
should mean) that two things can be substituted, that they behave the
same way.

A mathematical and CS definition in fact tracks the common English
meanings of these 2 words, and the language concerning Object.equals
that Patricia quoted does say exactly this: equals is an
implementation
of equivalence.

My point is that the common English, the mathematical and the CS
definitions of the two terms are pretty much the same. And
programming
languages that differentiate between the two also track those
definitions. We see that Java does.

I don't think Java follow those rules.

I assume that you consider equals to be equivalence and
== to be equality.

But it is not so.

Example:

public class ZZ {
public static void main(String[] args) {
int iv = 2;
double xv = 2.0;
System.out.println(iv == xv);
}
}

I do consider == to be equality (identity). Same object for references,
same value for primitives...that's equality.

But 2 and 2.0 are not identity equals.

No, but as I indicate I don't see that we ever compared 2 and 2.0 for
identity in your example. You can't do that with direct use of ==,
because of promotion.

This is one of the C-based hiccups of Java. For primitives of different
types we shouldn't even be asking the question ==?
An implementation of equals() I consider to be _an_ implementation
of an
equivalence relation; == is clearly another.

I'm not that perturbed by your example. Java == equality for primitives
of different types is what we define it to be, so defining it to be an
operation that takes place after primitive conversions is not wrong. In
effect we're not saying that (int 2) == (double 2.0), we're saying that
(double 2.0) == (double 2.0); the binary numeric promotion happened
before the ==.

That promotion is done by ==.

"Done by?" I don't see that. "Done on behalf of", sure. Unless a
bytecode guru tells me otherwise I expect that == just like + is
blissfully unaware that it could ever be asked to work with two
primitives of differing types.

Now, if each binary operator duplicates all the numeric promotion logic
I could see that they are responsible for it.

The expression is iv == xv. Everything in that expression is about ==.

The promotion is part of what the Java language describe for
that operator.

== is not testing for identity equals between the two operands for
simple types.

The fact that it is doing testing for identity equals as part
of what it does does not matter because it is not doing it
on the two operands.

Arne

If I parse what you just now said accurately, you're agreeing with me:
in this example == is not comparing 2 and 2.0.

AHS
 
A

Arne Vajhøj

Arne said:
Arne Vajhøj wrote:
On 12-03-2010 21:08, Arved Sandstrom wrote:
Arne Vajhøj wrote:
On 12-03-2010 05:26, Arved Sandstrom wrote:
Both terms actually have clear English meanings - "equality"
means (or
should mean) that two things *are* the same, and "equivalence"
means (or
should mean) that two things can be substituted, that they behave
the
same way.

A mathematical and CS definition in fact tracks the common English
meanings of these 2 words, and the language concerning Object.equals
that Patricia quoted does say exactly this: equals is an
implementation
of equivalence.

My point is that the common English, the mathematical and the CS
definitions of the two terms are pretty much the same. And
programming
languages that differentiate between the two also track those
definitions. We see that Java does.

I don't think Java follow those rules.

I assume that you consider equals to be equivalence and
== to be equality.

But it is not so.

Example:

public class ZZ {
public static void main(String[] args) {
int iv = 2;
double xv = 2.0;
System.out.println(iv == xv);
}
}

I do consider == to be equality (identity). Same object for
references,
same value for primitives...that's equality.

But 2 and 2.0 are not identity equals.

No, but as I indicate I don't see that we ever compared 2 and 2.0 for
identity in your example. You can't do that with direct use of ==,
because of promotion.

This is one of the C-based hiccups of Java. For primitives of different
types we shouldn't even be asking the question ==?

An implementation of equals() I consider to be _an_ implementation
of an
equivalence relation; == is clearly another.

I'm not that perturbed by your example. Java == equality for
primitives
of different types is what we define it to be, so defining it to be an
operation that takes place after primitive conversions is not
wrong. In
effect we're not saying that (int 2) == (double 2.0), we're saying
that
(double 2.0) == (double 2.0); the binary numeric promotion happened
before the ==.

That promotion is done by ==.

"Done by?" I don't see that. "Done on behalf of", sure. Unless a
bytecode guru tells me otherwise I expect that == just like + is
blissfully unaware that it could ever be asked to work with two
primitives of differing types.

Now, if each binary operator duplicates all the numeric promotion logic
I could see that they are responsible for it.

The expression is iv == xv. Everything in that expression is about ==.

The promotion is part of what the Java language describe for
that operator.

== is not testing for identity equals between the two operands for
simple types.

The fact that it is doing testing for identity equals as part
of what it does does not matter because it is not doing it
on the two operands.
Arne

If I parse what you just now said accurately, you're agreeing with me:
in this example == is not comparing 2 and 2.0.

It is. At the Java language level it is comparing those two.

That comparison consists of a promotion and an identity equality
test between one of the variables and a temporary variable.

Arne
 
A

Arved Sandstrom

Arne said:
Not to mention that quite often the problem does not happen
when debugging. Concurrency problems are frequent problems.
Often debugging requires optimization turned off and then
bad code may work. Etc..

In server programming debuggers are mostly a tool to
impress pointy haired bosses with.

I beg to differ. Not two weeks ago I saved a lot of time by debugging
down into a library used by a client's J2EE application; the problem was
almost certainly caused by *our* code, but it was manifesting in the 3rd
party library. Any other approach other than using a debugger would have
been considerably more time-consuming.

I've found that in J2EE programming, unless you're actually the guy
writing the guts of the app server, that a very small percentage of all
defects have anything to do with concurrency issues, provided that the
app programmers have half a clue. And debugging can often be the easiest
way to trace one's way through what can be rather convoluted paths of
execution.

AHS
 
A

Arved Sandstrom

jebblue said:
A man and woman are equal and yet very different.
Then clearly how they are equal needs to be qualified, does it not?

In fact when the word "equal" is applied in the sense of gender
equality, or equality before the law, or "all men are created equal",
such qualifications are made.

We make exactly the same qualifications in a programming language like
Java when we write equals() methods.

AHS
 
A

Arved Sandstrom

Patricia said:
I take the view that any multi-processor or multi-thread timing case
that cannot be proved impossible will happen sooner or later, even if
there is no known system test that can be guaranteed to produce it. That
means the code to handle it should be there, and should be tested.

Patricia

It seems to me that if you are sufficiently skilled in concurrency
programming that you can pinpoint a situation that you cannot test but
can't prove impossible, that rather than spend time writing code to
handle the execution of the "possibly impossible" code, and then testing
that handler code, that you might be better off simplifying your
original code in the first place.

AHS
 
S

Stefan Ram

Arved Sandstrom said:
Both terms actually have clear English meanings - "equality" means (or
should mean) that two things *are* the same

»Roughly speaking: to say of /two/ things that they are
identical is nonsense, and to say of /one/ thing that it
is identical with itself is to say nothing.«

Wittgenstein, Tractatus Logico-Philosophicus 5.5303

http://www.kfs.org/~jonathan/witt/t55303en.html
 
A

Arne Vajhøj

I beg to differ. Not two weeks ago I saved a lot of time by debugging
down into a library used by a client's J2EE application; the problem was
almost certainly caused by *our* code, but it was manifesting in the 3rd
party library. Any other approach other than using a debugger would have
been considerably more time-consuming.

I've found that in J2EE programming, unless you're actually the guy
writing the guts of the app server, that a very small percentage of all
defects have anything to do with concurrency issues, provided that the
app programmers have half a clue. And debugging can often be the easiest
way to trace one's way through what can be rather convoluted paths of
execution.

My experience is different.

The bad problems usually seems to only show up when running with
high load.

Completely impossible to use debugger with.

So a 50 MB log4j log file from each cluster member.

:-(

Arne
 
B

BGB / cr88192

Arne Vajhøj said:
My experience is different.

The bad problems usually seems to only show up when running with
high load.

Completely impossible to use debugger with.

So a 50 MB log4j log file from each cluster member.

:-(

a lot may depend on when, where, and how one uses threading...

admitted, most of my multi-threaded experience has been in C, so I am not as
sure how it compares with Java...


simply creating bunches of threads which work on the same code and data,
using only basic mutex-based synchronization, doesn't really turn out well.

instead, I tend to split threads along with the data, so usually each thread
is working on its own data mostly independent of the others.

for shared components, I tend to split threads along the component borders
as well, so rather than having 2 or more threads operating "within" a
component, nearly all requests are routed through a "mailbox", which may
serve to serialize activity (actually, it is sort of like event-driven code,
where usually a loop will serve as a message-dispatcher, or sleep when
idle).

this could be combined with the prior strategy, in which case one
"communicates with" a piece of data, rather than having its internals be
beaten on by bunches of threads at the same time.

note that the mailbox may be hidden behind the API, so the client code
doesn't directly see that the requests are being marshalled through a
mailbox (and, usually, locking/mutexes are used to synchronize access to the
mailbox, such as when adding or removing messages, ...).


admittedly, my approach to threading had been originally influenced some by
Erlang, and also I have not done much along the lines of large-scale /
highly-parallel code, so I am not really sure the hows and whys of thread
use in commercial SW...

a lot of my stuff tends to be largely sequential code with threads off doing
side tasks, with the threads mostly there to deal with the possibility of
threads, or to better utilize multi-core processors. as such, a lot of the
code is not "thread safe" per-se...


or such...
 
A

Arved Sandstrom

jebblue said:
There is no simple code in concurrent programming. Just have a good movie
ready when you get home.
Unless you're dealing with shared state most code that gets executed by
multiple threads is as simple (or not simple) as it would be if executed
by a single thread. Usually that means most code, period.

A competent developer should be able, through doing some professional
development on the topic, be able to often avoid shared state. If this
is not possible, the techniques to be followed in order to eliminate
concurrency problems are not exactly that difficult for a competent
programmer to grasp. And I used the word "eliminate" on purpose - not
"reduce", but "eliminate".

I don't doubt that 80-90 percent of the people who currently work as
programmers couldn't competently write reliable concurrent code, but
then OTOH they can't write reliable code period, so it's really got
nothing to do with concurrency. A software developer who can write
high-quality code can write high-quality concurrent code, and not have
to agonize over it either.

AHS
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,773
Messages
2,569,594
Members
45,122
Latest member
VinayKumarNevatia_
Top