Hacker News new | past | comments | ask | show | jobs | submit login

Yes in that case it's a win, but it becomes an anti pattern when you sometimes want to perform_new_feature() and sometimes you don't. I've seen it happen where you end up with many functions like:

  def a_and_b_and_c() {...}
  def a_and_b_and_c_error_checked_internally() {...}
  def a_and_new_and_b_and_c {...}
  def a_and_new_and_b_and_c_error_checked_internally() {...}
  def a2_and_b_and_c 
And so on. Basically you are just putting a memorization task on calling the names rather than using the underlying bits well. Learning the balances around this is one of those "craft" bits of programming.



This is a great discussion! I've always thought that after the basic "how do I even do this at all?" concepts of programming, the next most important concept to learn how to wield and to perpetually sharpen is the "don't repeat yourself" principle. Huge swaths of software engineering literature is dedicated (explicitly or implicitly) to when and when not to apply that principle.

Your parent's comment addresses by far the best argument for the principle, which is that any time something is likely to change in a snippet, copy-pasting should be undertaken rarely and thoughtfully. With the recognition that nearly any given snippet is likely to change in some way, we can conclude that nearly any copy-paste is "bad".

Your comment is then a great argument against the thoughtless application of the principle, recognizing that yes, granted, nearly any snippet will change, but the required changes may well not be uniform.

Personally, I think it still makes sense to pull out shared behavior for the period of time in which it is shared. During that period of time, all you have is a guess that the code might eventually diverge. If there comes a point in time when you do want the code to diverge, it is straightforward to copy-paste it back out, if branching or creating a similar method with the differences is a worse option. On the flip side, if you haven't pulled out the shared behavior, and you discover that you want to change it everywhere in the same way, it is far less straightforward to go find all those places, or even be aware that you need to do so.

Another point is naming by purpose rather than behavior. To continue your example, it makes sense to ask why one method checks internally and the other externally? What different purposes do the different checking styles have? If there are good answers to questions like that, the methods can be named better, and it becomes less about memorizing name than understanding when to use which.

Of course none of this is at all black and white, and I think you're spot on that this is one of those craft bits, and among the most important!


One thing to keep in mind here... some of those "you can't predict" things are utterly predictable in light of experience. That part can't be faked to well, there aren't a good set of rules about it. But experience is a good teacher, and you do eventually learn to be reasonably accurate about when to do C+P vs DRY, even if you can't explain the why of it.

I think it comes down to this - when I first learned to code, it was hard enough to keep track of a couple different functions. As experience came, what I could track and reason about and keep in my mental model grew, so now that I've got some experience I can see more of how the whole system will grow and interact. I'm not really smarter per se, but rather I just have more practice as putting it all together.


Couldn't agree more. It's definitely a function of the extent to which you're guessing, which is a function of your experience solving very similar problems.

I do think we (programmers? people?) have a tendency to avoid inspecting the experience we already have. We thought hard about some pattern a few times and came up with some instincts based on that experience, which is great, but we should make sure to double-check those instincts from time to time.


It's actually pretty well predictable if you think about it in terms of encapsulation and core functionality. If this functionality is an external requirement to the purpose of the components, then it absolutely should exist as its own component that coalesces that functionality into a single location. However, if that functionality is core functionality to the components, and they just happen to do the same thing in the same order... Then that's where you'll eventually see divergence if you try to link them to a third component. In those cases, you should either be trying to join the two components into one, or just happily leave them as separate components that just happen to perform some common operations by way of their primary function.


In at least two of your examples you could pass in an error handling function (or class) and it would still be a net win.

Analyzing this further would require having a more fleshed out example.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: