Ruby programming best practice

As a dynamic language, Ruby is much more flexible and easier than other
language I use, for example, Delphi. However, there is a “intrinsic”
problem
in ruby programming – a typical error I get again and again:

private method `split’ called for nil:NilClass

The following statement:

array.pop.split(’,’)

will generate this error if array is empty. Of course I can use rescue
Exception statement, as a matter of fact, I do use that. Even after 4-5
years, some of my program still generate these kind of error
occasionally.

Real-world situation is just too complicated. How do we write robust AND
elegant code, and let our thinking smooth rather than considering
exceptions
all the time?

Experts, please tell us, your best practice, facing this “intrinsic”
problem
of dynamic languages…

Thanks,
Shannon

On 01/11/06, Shannon F. [email protected] wrote:

will generate this error if array is empty. Of course I can use rescue
Thanks,
Shannon


Be the first to hear what’s new at MSN - sign up to our free newsletters!
MSN

There’s a number of ways to handle this. If the array is possibly
empty you could check beforehand and return a default result
array.empty? ? [‘’,‘’,‘’] : array.pop.split(‘,’)
If the code that handles the split result can handle empty arrays then
you could just
array.pop.to_s.split(‘,’)
or
String(array.pop).split(‘,’)

Farrel

Shannon F. [email protected] wrote:

will generate this error if array is empty.
If my goal is that I just want skip the step if it can’t be done, I like
this kind of thing:

array.pop.split(‘,’) if array.last

If array itself could be nil then I have to add:

puts array.pop.split(‘,’) if array and array.last

m.

How do we write robust AND elegant code, and let our thinking
smooth rather than considering exceptions all the time?

array.pop.split(‘,’)

(array.pop || ‘’).split(‘,’)

Just an example…

gegroet,
Erik V. - http://www.erikveen.dds.nl/

My question is: is there a way to find out these spots in my program,
like a
compiler do in Delphi/C.

e.g.:

def test(arr)
arr.each do |a|

end
end

in the above code, I will naturally assume arr is an array. Only after
the
program is in use (may be even a year later!), I suddenly got a report
of
error caused by “arr is nil”.

In delphi, for example, such error is detected while compile, e.g. (a
might
not have been initialized). I know Ruby works differently, but is there
a
way (or a project) exists, like a normal compiler, or something like
FxCop
for the C# language?

Thanks,
Shannon

Shannon F. wrote:

/ …

In delphi, for example, such error is detected while compile, e.g. (a
might
not have been initialized). I know Ruby works differently, but is there a
way (or a project) exists, like a normal compiler, or something like FxCop
for the C# language?

Basically, Ruby, unlike compiled languages, in many cases won’t detect
an
undefined entity until it encounters it at runtime. So you have several
options. You can test your program before releasing it (seems the
professional thing to do), or you can write a validator that checks for
orphan variable names. Such a validator would be fairly easy to write –
tear the source apart, filter out all the Ruby reserved words, and list
those words that appear only once – some will be defined but unused,
some
will be used but not defined.

This won’t help you with scoping issues, of course, unless you traverse
the
program’s scopes. That begins to be more complex.

The first approach (exhaustive testing) is preferred. I would never
release
a program that had not had every line executed in typical conditions.

Also, Ruby isn’t that different from compiled languages. As to the
latter, a
null pointer will in many cases go unnoticed until it creates a runtime
error when the code the pointer belongs to is executed, as you say,
perhaps
years later.

Paul L. wrote:

Also, Ruby isn’t that different from compiled languages. As to the latter, a
null pointer will in many cases go unnoticed until it creates a runtime
error when the code the pointer belongs to is executed, as you say, perhaps
years later.

Unless you use one with compile-time null-safety checking. Defensive
compile-time DbC does loads for making your program’s behaviour
predictable.

(Disclaimer: I am not an Eiffel weenie. Weird syntax, TOO_MANY_ALLCAPS,
etc.)

David V.

On 11/1/06, Shannon F. [email protected] wrote:

Experts, please tell us, your best practice, facing this “intrinsic” problem
of dynamic languages…

Use Unit Tests.

With automated unit testing, the benefits of static typing, bounds
checking,
null checking etc. start to disappear, as you typically write tests for
these
scenarios anyway.

So dynamic languages with unit testing can do away with these features
and get better productivity bonuses, and more flexible coding styles
for no cost.

It does mean that you are committed to using unit tests though for any
significant work. In reality all significant projects really need
them, but you will miss them earlier in a dynamic language.

Richard C. wrote:

these
scenarios anyway.

The benefits disappear as you reimplement them in test cases anyway?
Insert comment about wheel reinvention here.

Also, compiler checks fail fast. That’s a Good Thing ™. Bounds checks
also fail faster than a NoMethodError elsewhere on the stack. Unit test
checks that noone thought of coding yet don’t fail fast - having to
cover your whole code with checks for let’s say public object contracts
isn’t too gratifying work, and I wouldn’t be surprised if a lot of
people “didn’t get around to it”. And believe it or not, knowing I
-have- these checks in a language compiler / runtime does mean I
wouldn’t even dream of unit-testing for them. DRY.

The point is, to achieve robustness, amongst other things, trivial
errors must cause a failure, this must manifest itself as close to the
origin of the error as possible, and be detected immediately. Whether
using design-by-contract checks, IDE templates of common defensive
programming checks on method arguments / return values, or exhaustive
testing, special cases have to be detected and handled specially.

So dynamic languages with unit testing can do away with these features
and get better productivity bonuses, and more flexible coding styles
for no cost.

Except you end up with a larger mass of unit test code. Which, make no
mistake, is code, can be buggy, and needs to be developed and
maintained, and if you want to avoid redundancy, also well-designed.

(Pop Quiz: how often do you test on array index bounds being valid? How
reusable is the test code? A Test::Unit extension library of reusable
pre / postcondition at method parameter and object state scope around
method calls would be interesting, if we’re swapping compile-time
contract checks for unit-test-time ones.)

So, risking to reinforce my troll position, I’ll postulate that the
productivity increase from dynamic typing is a delusion. From what I
have seen, a decently designed type hierarchy, and some degree of type
inference would do away with 90% of the trivial anecdotes demonstrating
time savings. In Ruby, I see more productivity gainers in
metaprogramming facilities, and the powerful object model - these aren’t
at all exclusive with type / contract checking. As of yet I remain
unconvinced that extensive use duck typing and eval’ing public methods
into objects is more good than harm in the long term. I blame years of
doing code maintenance that taught me that most programmers (myself
included) think they’re much smarter than they are and are way too
impressionable by “clever ideas” (= horrid hacks) to think of edge
cases.

(To wit: my last work project wrapped ORM in domain-specific facades,
complete with some rudimentary transaction management. Quick, easy to
mechanically churn out new facade code, no dependency on a declarative
TX mgmt. solution, read very nicely and was easy to use. Fast forward to
several junior developers, including me, being put to work on a new
feature, mostly guessing around from existing code. I was the first to
ask for some non-trivial code that was working with the model to be
reviewed by a senior, and got blinked at for doing what was supposed to
be an atomic change in multiple facade calls, i.e. multiple
transactions. Of course by that time the others had commited several
show-stopping bugs related to optimistic locking that created
unmodifiable zombie records in the database that required a manual edit
of the testing server’s database to bring things back on track. And
thrashed the development DB of everyone else working on the module.
However, had the optimistic locking checks not been there, what was
essentially a potential invalid database change would have passed
silently and concurrent users would send the data integrity down the
proverbial creek with no paddle once out of testing because the
transaction-wrapping facades would hide the problem.)

David V.

On Nov 2, 2006, at 4:27 PM, David V. wrote:

null checking etc. start to disappear, as you typically write
tests for
these
scenarios anyway.

The benefits disappear as you reimplement them in test cases anyway?
Insert comment about wheel reinvention here.

I quit reading right here.

It’s not reimplementation!

You need to be testing your software anyway, even if you are using
the perfect language that protects you from all your errors. Since
you’re doing that anyway, this is just a free side effect for your
good habits.

James Edward G. II

James Edward G. II wrote:

With automated unit testing, the benefits of static typing, bounds

I quit reading right here.

It’s not reimplementation!

You need to be testing your software anyway, even if you are using the
perfect language that protects you from all your errors. Since you’re
doing that anyway, this is just a free side effect for your good habits.

That’s what the original quote states though. “You typically write tests
for these scenarios anyway.”, amongst those scenarios being some that
can be checked using static analysis in a suitably more predictable
language. If you’re using a dynamic language, and covering checks for
those scenarios in unit tests, then you’re reimplementing what a
contract-verifying compiler / runtime would catch in them, quite simply
because without such a compiler / runtime you don’t have much choice if
you want your code to be robust along those axes. In and of themselves,
unit tests are (supposed to be) a complement to (substitute for the
absence of) language-provided unit contract checks - since they’re
arbitrarily user-defined, they are to be used to check dynamic aspects
of those contracts that statical analysis (if there is any) can’t.
Generally the unit testing methodology gets more awkward when you need
to test complex emergent behaviour that comes from the cooperation of
these units, and equating them to “testing your software” is just
misleading.

If you gain enough coverage for dynamic unit contracts, I’ll admit it’s
probably safe to assume that you have the more trivial checks that
statical analysis can do covered - in that case you can do away with
anything that would be reimplementing a compiler’s checks. However, such
coverage takes nontrivial effort to create and maintain, and usually at
least I want to be able to reason on a code unit’s correctness at least
to some extent while expending no / minimal effort on test code for the
“D’oh” type of error. For example, I’d very much like if I could tag a
variable to disallow assigning a nil into it and raise an exception when
that happens -immediately-, instead of getting a NME someplace else in
the code. I’d be hard to convince removing that check into a unit test
would be more concise than having language support for it.

David V.

M. Edward (Ed) Borasky wrote:

By the way, testing can only show the presence of defects, not their
absence.

:slight_smile:

Is there anyone here who didn’t know that already??

Phlip wrote:

M. Edward (Ed) Borasky wrote:

By the way, testing can only show the presence of defects, not their
absence.

:slight_smile:

Is there anyone here who didn’t know that already??

I don’t really know. Invariably, someone will post a question about
tools, or the language, or strong typing, or compile-time checking, or
processes, or practices, or just about anything, and the answer will
come back, “testing is good”. I know testing is good. But sometimes I
wonder if we aren’t expecting too much from testing.

David V. wrote:

[snip]

By the way, testing can only show the presence of defects, not their
absence.

:slight_smile:

David V. wrote:

By the way, testing can only show the presence of defects, not their
absence.

If it is not present, is it not absent?

M. Edward (Ed) Borasky wrote:

I don’t really know. Invariably, someone will post a question about
tools, or the language, or strong typing, or compile-time checking, or
processes, or practices, or just about anything, and the answer will
come back, “testing is good”. I know testing is good. But sometimes I
wonder if we aren’t expecting too much from testing.

At this point in the discussion, those who advocate relentless testing
often
point out they never use the Ruby debugger.

Maybe I should; I might learn the control flow thru some of those wicked
continuances in all these Ruby libraries. I certainly debug a lot, with
trace statements.

But advanced debugging certainly isn’t a best practice. It can prove the
absense of bugs much worse than testing can! :wink:

David V. wrote:

James Edward G. II wrote:

With automated unit testing, the benefits of static typing, bounds

I quit reading right here.

It’s not reimplementation!

You need to be testing your software anyway, even if you are using the
perfect language that protects you from all your errors. Since you’re
doing that anyway, this is just a free side effect for your good habits.

That’s what the original quote states though. “You typically write tests
for these scenarios anyway.”, amongst those scenarios being some that
can be checked using static analysis in a suitably more predictable
language. If you’re using a dynamic language, and covering checks for
those scenarios in unit tests, then you’re reimplementing what a
contract-verifying compiler / runtime would catch in them, quite simply
because without such a compiler / runtime you don’t have much choice if
you want your code to be robust along those axes. In and of themselves,
unit tests are (supposed to be) a complement to (substitute for the
absence of) language-provided unit contract checks - since they’re
arbitrarily user-defined, they are to be used to check dynamic aspects
of those contracts that statical analysis (if there is any) can’t.
Generally the unit testing methodology gets more awkward when you need
to test complex emergent behaviour that comes from the cooperation of
these units, and equating them to “testing your software” is just
misleading.

If you gain enough coverage for dynamic unit contracts, I’ll admit it’s
probably safe to assume that you have the more trivial checks that
statical analysis can do covered - in that case you can do away with
anything that would be reimplementing a compiler’s checks. However, such
coverage takes nontrivial effort to create and maintain, and usually at
least I want to be able to reason on a code unit’s correctness at least
to some extent while expending no / minimal effort on test code for the
“D’oh” type of error. For example, I’d very much like if I could tag a
variable to disallow assigning a nil into it and raise an exception when
that happens -immediately-, instead of getting a NME someplace else in
the code. I’d be hard to convince removing that check into a unit test
would be more concise than having language support for it.

David V.
Hi David,

Im looking for Ruby P.mer to work in Bratislava/SK.
Please let me know if you are interested or if you know someone.

Thanks and sorry for any inconvenience.

Rafael Vieira - IM: [email protected]

On 3-Nov-06, at 8:09 AM, Brad T. wrote:

David V. wrote:

By the way, testing can only show the presence of defects, not
their
absence.

If it is not present, is it not absent?

As long as you can guarantee that you are testing for all possible
defects then I suppose that argument works :slight_smile:

If there is a defect for which you don’t have a test then testing
will not show its presence, and it is not absent.

Mike

Mike S. [email protected]
http://www.stok.ca/~mike/

The “`Stok’ disclaimers” apply.