Tired of non-semantic testing to make up for dynam

2019-06-16 04:52发布

I used to do a lot of web programming in Rails (PHP before that) before I started studying computer engineering.

Since then, I've done a lot of school work in C, and some personal stuff in Objective-C (Mac stuff). I learnt to love static typing.

But now I'm having to do some professional web development (freelancing) and have picked up Rails once again. I'm finding it really annoying to write non-semantic type-checking tests. I was getting those for free from C and Objective-C compilers. I loved hitting Build and having the system check all my code to see that A can call B, B can call some obscure library C, etc. All I had to do was test the semantics. But with Rails, I'm the compiler. :(

Has anyone treaded this same path? Are my only options for web development ASP.NET MVC with C# and Java + x framework? Looking for some suggestions, or even some sympathy... :P

By the way, I make a specific reference to Rails rather than Ruby because I don't mind Ruby's dynamic nature for simple stuff like scripting or what not. But since Rails depends on so many gems and since one usually adds a number of other gems, the dynamic typing becomes an issue.

Thanks!

edit:

I followed up on pst's suggestion and looked into Scala. In reading the book Programming in Scala, written by the language's creator, Martin Odersky, I came accross this bit of text that in many ways expresses my concerns and a bit more. Very interesting reading.

Taken from page 52 of Martin Odersky's Programming in Scala:

Scala is statically typed

A static type system classifies variables and expressions according to the kinds of values they hold and compute. Scala stands out as a language with a very advanced static type system. Starting from a system of nested class types much like Java’s, it allows you to parameterize types with generics, to combine types using intersections, and to hide details of types using abstract types. These give a strong foundation for building and composing your own types, so that you can design interfaces that are at the same time safe and flexible to use.

If you like dynamic languages such as Perl, Python, Ruby, or Groovy, you might find it a bit strange that Scala’s static type system is listed as one of its strong points. After all, the absence of a static type system has been cited by some as a major advantage of dynamic languages. The most common arguments against static types are that they make programs too verbose, prevent programmers from expressing themselves as they wish, and make impossible certain patterns of dynamic modifications of software systems.

However, often these arguments do not go against the idea of static types in general, but against specific type systems, which are perceived to be too verbose or too inflexible. For instance, Alan Kay, the inventor of the Smalltalk language, once remarked: “I’m not against types, but I don’t know of any type systemsthat aren’t a complete pain, so I still like dynamic typing.”

We hope to convince you in this book that Scala’s type system is far from being a “complete pain.” In fact, it addresses nicely two of the usual concerns about static typing: verbosity is avoided through type inference and flexibility is gained through pattern matching and several new ways to write and compose types. With these impediments out of the way, the classical benefits of static type systems can be better appreciated. Among the most important of these benefits are verifiable properties of program abstractions, safe refactorings, and better documentation.

Verifiable properties

Static type systems can prove the absence of certain run-time errors. For instance, they can prove properties like: booleans are never added to integers; private variables are not accessed from outside their class; functions are applied to the right number of arguments; only strings are ever added to a set of strings.

Other kinds of errors are not detected by today’s static type systems. For instance, they will usually not detect non-terminating functions, array bounds violations, or divisions by zero. They will also not detect that your program does not conform to its specification (assuming there is a spec, that is!). Static type systems have therefore been dismissed by some as not being very useful. The argument goes that since such type systems can only detect simple errors, whereas unit tests provide more extensive coverage, why bother with static types at all?

We believe that these arguments miss the point. Although a static type system certainly cannot replace unit testing, it can reduce the number of unit tests needed by taking care of some properties that would otherwise need to be tested. Likewise, unit testing cannot replace static typing. After all, as Edsger Dijkstra said, testing can only prove the presence of errors, never their absence. So the guarantees that static typing gives may be simple, but they are real guarantees of a form no amount of testing can deliver.

Safe refactorings

A static type system provides a safety net that lets you make changes to a codebase with a high degree of confidence. Consider for instance a refactoring that adds an additional parameter to a method. In a statically typed language you can do the change, re-compile your system and simply fix all lines that cause a type error. Once you have finished with this, you are sure to have found all places that need to be changed. The same holds for many other simple refactorings like changing a method name, or moving methods from one class to another. In all cases a static type check will provide enough assurance that the new system works just like the old.

Documentation

Static types are program documentation that is checked by the compiler for correctness. Unlike a normal comment, a type annotation can never be out of date (at least not if the source file that contains it has recently passed a compiler). Furthermore, compilers and integrated development environments can make use of type annotations to provide better context help. For instance, an integrated development environment can display all the members available for a selection by determining the static type of the expression on which the selection is made and looking up all members of that type.

3条回答
虎瘦雄心在
2楼-- · 2019-06-16 05:13

As you mention Rails, and given that you have become interested in Scala, you should definitely check Lift. Here's a 2008 interview with its creator, and a 2009 presentation (video), which I link because, though old, they compare Lift with alternatives in other languages.

If Lift is not your thing, though, rest assured that there are other Scala web frameworks.

查看更多
一夜七次
3楼-- · 2019-06-16 05:35

This type of testing is not typically done in Rails. Instead of being annoyed by having to do it, consider not worrying about it. Or perhaps do a better job of explaining why you think it's an issue, since most Rails programmers don't.

Update

The person who wrote test_include_with_order_works is making sure that Rails will interpret a symbol the same as a string in this particular case. That doesn't seem like something you'd have to test, since Rails has already provided and tested this functionality for you. Frankly, I'm a little surprised that anyone would even worry about whether or not a symbol will function like a string. We all know that it can and often does.

In general, I think that the Rails framework has to ensure things that you do not so that its implementation will conform to its design. I believe that the working philosophy in dynamically typed languages is that client code will not pass in parameters that will break the methods they're calling. If they do, they get no use out of calling the methods. You don't have to waste your time making sure that your methods will throw exceptions when provided too many parameters, when your method can just as easily (and should) ignore the extra parameters.

So, I'm not sure if the examples you've provided really demonstrate the need for non-semantic testing in Rails.

查看更多
狗以群分
4楼-- · 2019-06-16 05:38

This is one of my "gripes" about dynamic languages. I want to test for semantics, not type errors ;-) That being said, a good testing framework/setup is really a must in all non-trivial situations and good code-coverage and tested requirements is/are important.

If you do want to go down the static-typing path on the JVM (I have), I would highly recommend looking at Scala. Coming from Ruby, it's far less painful (and actually lots of fun in different ways) than going to Java. You get to "keep" the things you take for granted -- an expression-based syntax, closures, the ability to omit types in many places (not as open as Ruby, but you do get compile-time type checking ;-), everything(*)-is-an-object OO, unified accessor methods, ability to construct DSLs easily, and sugar -- and get the benefits of a statically typed language with local type inference, pattern matching, a relatively rich collection framework, and decent integration with Java (including the numerous web-frameworks, there are some Scala-specific frameworks as well which leverage the Scala language).

C#3.0/4.0 (and .NET3.5+) isn't too shabby either (but avoid C#2.0, which is now hopefully a relic), with the introduction of LINQ/closures, basic type inference and other nice language features I find it "acceptable" for most tasks (take a guess how I would rate Java as a language ;-). However, C# is a CLR-target language (there is/was a .NET Scala port, but I am not sure of the status -- it is not the main target platform though).

Since I have mentioned Scala, I should also mention F# (now an "official" .NET language) which takes the "Functional with OO" approach being similar to OCaml -- Scala is more of the reverse and I would describe it as "OO with Functional". I have heard arguments for/against F# compared to C# w.r.t the type system, but have no practical experience with F#. You may or may not like the paradigm shift.

Happy coding.

查看更多
登录 后发表回答