12 ms·
Couldn't agree more. If the behaviour or outcome of a function depends on the current time, then the current time is an input to that function. Inputs to functi
by h2s 14y ago
Couldn't agree more. If the behaviour or outcome of a function depends on the current time, then the current time is an input to that function. Inputs to functions should be declared explicitly as arguments where possible.
The existence of Time.now and the fact that it's possible to stub this method are not in themselves a good enough justification for excluding the current time from the list of explicit inputs. The length of the list of a function's inputs is one of the indicators of whether a function has too many disparate inputs and therefore too many responsibilities. Obscuring this number is likely to lead to more cluttered code.
In DHH's example, Time.now should be called somewhere higher up the call stack where its return value doesn't matter to the outcome of the function in which it resides. It should look like this:
def publish!(time)
self.update published_at: time
end
That way the inputs are clear. It also happens to mean that you can test it without requiring the linguistic flexibility provided by Ruby, but that doesn't mean that testability in inflexible languages is the only reason why it's the right thing to do.
- mpweiher 14y agoWell put! Another reason to grab the time once and distribute it is that you typically don't want the passage of time during processing to affect results, that is, you want your system to perceive "atomic" time. Would suck if half your update is December 31st 23:59, and the other half January 1st, 00:00.
- Xylakant 14y agoIn ruby you'd use timecop for time sensitive tests. It allows to freeze time and move it forward/backwards in specified increments.
- chrismsnz 14y agoI think the argument is that solutions such as Timecop are a hack for stubbing dependencies that are pulled out of global scope.
- Xylakant 14y agoAt some point you will need to test the unit that create the time object. With DI you could swap out the implementation of time that gets created and use some implementation that returns a constant time. But that still pulls the information about the implementation to use from some global scope: The DI library needs to know which implementation to use, and that information will come from some global scope - a configuration of some kind, thus making that argument a moot point. In ruby you can modify the classes at runtime to mock such functionality. You could argue that modifying the time implementation is some form of DI - you're changing the implementation at runtime.
- mercurial 14y agoDI is still relevant for dynamic languages, for two reasons: - it lets you put your dependencies all at the same place - it lets you define clearly what the dependencies of a piece of code are, without having to look at internals which may change tomorrow without warning One thing I find aggravating with dynamic languages is that metaprogramming is a crutch people tend to rely on because they can't be bothered to write clean APIs. In my experience, it's particularly prevalent in Ruby. Of course, the lack of dynamism can be a problem in statically typed languages, giving Spring's or Hibernate's stacktraces of hell with piles of proxy objects.
- mpweiher 14y agoVery succinctly put. Newspeak solves this by eliminating the global scope and eliminating "import". Every module is parametrized with everything it needs.
- h2s 14y agoTimecop.freeze(Date.today + 30) do assert joe.mortgage_due? end vs assert joe.mortgage_due?(Date.today + 30)
- deleted 14y ago[deleted]
- yummyfajitas 14y agoYou can also use the SQL value of NOW(). It's always set to the start of the current transaction. http://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT http://www.postgresql.org/docs/current/static/functions-date...
- mpweiher 14y agoWell you could, but now you've coupled your system to the database.
- rapind 14y agoWhich if we're going to be honest is a hugely overhyped issue. Note: I base this on how often I've swapped out the database for a project in the past 15 years (maybe twice?) of writing mostly database backed software.
- mpweiher 14y agoIt's not the swapping, it's the (configuration) complexity and test performance, among other issues. See http://www.confreaks.com/videos/759-rubymidwest2011-keynote-architecture-the-lost-years http://www.confreaks.com/videos/759-rubymidwest2011-keynote-.... I particularly like the example he gives near the end of always thinking they need to add the database and finally, after shipping to a bunch of customers, figuring out that they didn't actually need it. In terms of test performance, I frequently see blog posts about reducing the time for a couple of hundred tests from half an hour to 15 minutes. marcel@localhost[~]time testlogger -a MPWFoundation ObjectiveSmalltalk ObjectiveXML EGOS EGOS_Cocoa MPWSideWeb tests:1: warning: 1055 tests (of 1055) executed, 0 failures (100 % success rate) real 0m2.939s user 0m1.607s sys 0m0.438s
- Xylakant 14y agoYour example is not DI. Your argument is in general sound, but it's tangential to the problem of DI. Your time object needs to be created at some point. DI would allow you to swap out the implementation of the Time class at that point. In general, the time example is stupid. A better example is maybe that you have an app that wants to make http calls. Depending on the interpreter and the os you might want to switch the http library. Net/HTTP is a solid default that runs everywhere, but there are faster alternatives if you're on MRI only. In java, you need DI to solve this problem. In ruby, you don't.
- chrismsnz 14y agoThis is just another example of how we're learning the lessons of FP about 20 years late. I'm no expert - I've only muddled with FP languages (mostly Haskell). But what I learned there deeply affected my everyday imperative and OOP coding. Pure functions make for testable, clean code.
- dasil003 14y agoGood functional languages like Haskell also offer better abstractions so we don't have to suffer tired debates around Java methodologies.
- lmm 14y agoThese are orthogonal axes. Good nonfunctional languages like Python also offer better abstractions than Java, and bad functional languages don't.
- chii 14y agoi dont believe python offers any better abstraction over java. Less verbose, sure, but not "better" abstractions.
- dasil003 14y agoYes they're all just Turing machines. You can implement a Lisp interpreter in Java, etc, etc.
- mercurial 14y agoIt has generators, for instance, which makes it a lot easier to implement lazy data structures. Or context managers (though Java 7 has it too). And first-class functions, which beats defining one-method interfaces to get a callback any day of the week.
- dasil003 14y agoAgreed. However I don't need to waste my breath advocating for Ruby over Java. DHH apparently has plenty of time to devote and never seems to tire of it, even after nearly a decade.
- alexatkeplar 14y agoTotally agree. The oddest thing is the example DHH picked and the language he used: > If your Java code has Date date = new Date(); buried in its guts, how do you > set it to a known value you can then compare against in your tests? Well, you > don't. So what you do instead is pass in the date as part of the parameters > to your method. You inject the dependency on Date. Yay, testable code! It suggests that the whole of functional programming has completely passed him by. To re-write that paragraph: If your Java function has Date date = new Date(); buried in its guts, then you have a side-effect which makes it much harder to reason about what that function is doing. Pass the date in as a function argument, and you now have (or are closer to having) a pure function: pure functions have no observable effect on the execution of your program, other than to compute a result given their inputs. A pure function like this is easier to reason about, easier to test, and easier to parallelize. It just feels like DHH is fighting yesterday's battles. Dependency Injection in Java? David, the 1990s called and asked for their book on Spring back. Why can't we be comparing Ruby's approach to something like the cake pattern in Scala (http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di/ http://jonasboner.com/2008/10/06/real-world-scala-dependency...), or even the pure keyword in Rust (http://static.rust-lang.org/doc/rust.html#pure-functions http://static.rust-lang.org/doc/rust.html#pure-functions)? If DHH spent some time building something with Clojure/Haskell/OCaml/Rust/Scala/F# and less time racing cars, he might come up with some interesting views again.
- mattmanser 14y agoDo you pass Random in? Or how about new file streams? Hell why should we even bother with local variables, lets just promote everything up the chain? Sure our method signatures would get longer and longer. We'd have to keep declaring variables again and again even though they're all used in one place. There's a line where it gets absurd as it makes the code more complex. There's no good reason to be adding a date variable to your method signature just to make everything functional. Because you then have to declare and initialize that variable extra times. That's the opposite of code reuse, that's bad programming. What happens when you need to refactor the method? What if the method call is in a different site to the date declaration? Why do that to yourself? This hasn't got anything to do with functional programming. It's just simply about DRY and pointing out dogmatic coding approaches like DI being applied without actually understanding why you're doing it.
- darrencauthon 14y agoSo you'd say that this code is better: post.publish Date.now than post.publish When the publishing will only happen now? I'm a Ruby developer who disagrees with DHH strongly on this issue, as well as his stance on SOLID and testing. I also have years of experience in C#, using TDD and using dependency injection and IoC containers everywhere. However, I think there's a point to be pulled from DHH's post about unnecessary ceremony around dependency inversion (the more important thing) and static languages. If I were to write this "publish" method in C#, I couldn't even put it in the Post object. It all looks fine and dandy now, but what happens when the first wave of change comes in? Say, if we were to have to stamp an "updated_by" field? Then we get to this: def publish(time, updated_by) self.update publish_at: time, updated_by: updated_by end And so on, and so on. The dependencies have to be passed to the object, so every dependency flows up to become dependency on any object that uses it. And since having method calls like ".publish(Date.now, CurrentUser.name)" everywhere really stinks for readability and DRY, and since I can't make Date.now a constructor requirement to create a Post, I always found myself having to do this (in C#): public class PostPublisher private IDate date; private ICurrentUser; public PostPublisher(IDate date, ICurrentUser){ this.date = date; this.currentUser = currentUser; } public void Publish(post){ post.updated_by = currentUser.name; post.published_on = date.now(); // save???? } } ... and let the IoC container wire this sort of stuff up. Dependencies tied to this operation are isolated (as well as every other operation). But UGH, is this any better? So many classes and LOC just to do simple things. I just don't see a good solution to this, and I'm not satisfied writing "good" code that handling is a pain. I decided to try Ruby as it seemed to have answers for this, and it does. You can call the ability to use the Date object and still change it a "global variable" or "linguistic flexibility," but the truth is: In the applications we write, published_at is only going to be set to Date.now and updated_by to the currently logged in user. And as soon as we write the application and deploy, it will serve the business needs wonderfully. As a C# developer, I justified the costs of doing these end-arounds as necessary to maintain a TDD'd codebase that could easily be changed later. As a programmer, though, I can't justify the costs of writing dozens of lines of code when I can TDD one line of code. And I sure as heck don't want to eat the costs myself. So today, I just do it with one line of code really fast and make the client happy.
- wyuenho 14y agoI think most of you are missing the point. I think the example DHH used should be interpreted as calling post.publish! is easier than post.publish! Time.now Since when you publish, the time of publication is the only value that make sense. Now, you can argue that you can set a default argument to Time.now, but that's weird code that only make sense if you need to supply a value other than Time.now, which you will only do in testing, so the code in DHH's example is optimal. The point of DHH's blog post is, in a flexible enough language like JS, Ruby and Python, you can still test pretty much anything without DI. I honestly don't know any other reason why one would want to use DI other than making Java testable. In fact, you don't even need that in Java anymore because you can use Mockito these days and it can mock private stuff. The stupidest reason I keep hearing over and over again is DI makes it possible to change an implementation at runtime. Wait what? Don't you have to change the application.xml file in Spring and restart the app? AFAIK there's nothing in Javaland that lets you listen to config file changes and then magically reconfigure the app context and make a copy of said context as a thread local in every new thread. Correct me if I'm wrong please. Even if there was, what magical alien land would require such a "feature", and such feature is so important that requiring your entire code base to be littered with all these indirect abstractions acceptable?
- Xylakant 14y agoDI to swap out an implementation at runtime is an extreme edge case, though "runtime" may mean "the app can determine at startup which implementation makes sense". If you want to have a look at a nice usage of DI, peek into the source of Elasticsearch. You can swap out pretty much any relevant part of the implementation. You can for example change the HTTP acceptor to provide authentication [1] or add SSL [2] So there are cases where changing the implementation makes sense. [1] https://github.com/Asquera/elasticsearch-http-basic https://github.com/Asquera/elasticsearch-http-basic [2] https://github.com/Asquera/elasticsearch-ssl-transport-plugin https://github.com/Asquera/elasticsearch-ssl-transport-plugi...
- wyuenho 14y agoFor which any simple configuration mechanism will do, and as long as you stick with coding to the interface, the problem is solved. So while technically this is called DI in Java, it's simply code taken as granted in many dynamic languages because of duck-typing. So yes, this is totally valid use case, but one with no need for a DI container in dynamic languages to solve.
- twelvechairs 14y agoRegarding the 'publish!' method, the post linked to in DHH's example (http://weblog.therealadam.com/2013/01/03/design-for-test-vs-design-for-api/ http://weblog.therealadam.com/2013/01/03/design-for-test-vs-...) explains this as a 'trade off' - the summary of the other side being that if you don't pass 'time' to it you get a simper API. This is somewhat more nuanced than your and DHH's conflicting opinions, and perhaps I show my inexperience (as primarily a ruby programmer), but this seems very sensible to me. It also reasonably describes why DHH's opinions may differ from others because of the focus of his work (in addition to the language used).
- masklinn 14y agoI would have gone with "sane defaults": def publish!(time=Time.now) self.update published_at: time end keeps the simple case simple (self.publish!) but allows injection if needed (custom publication time or testing stub). My language of choice being Python, I tend to do that rather often for injecting "classes" which will have to be instantiated: the function or object constructors takes callables (since Python's instantiation is just calling) with defaults provided.