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

Say you have a simple application and the only possible use case is to increment an account by $100.

Should you write the function as

  account.credit_100_dollars()
  account.credit_dollars(100)
  account.process_transaction(new credit_transaction(100))
I have to say I prefer option 3 to option 1 even considering YAGNI. It's better OO, it's more testable, there's more potential for reuse in other parts of your application etc.

Interestingly, when I started as a developer I might have written something closer to the first option. After a few years I would have gone for something closer to the third option. Nowadays, I'd probably go for the second option as I've hopefully learnt to strike a balance in the abstractions I create.




None of those three examples expresses intent. Why are you crediting 100 dollars? Is it a refund, is it a loyalty reward, a signup bonus?

    account.award_loyalty_bonus()
    account.award_welcome_bonus()
This is closer to the first option, but will actually model the behaviour of your application using domain terms.


If I understand what benjamin is saying, these would be written as:

account.process_transaction(new loyalty_bonus(bonus_amount))

or possibly

account.process_transaction(new bonus(loyalty_bonus_amount))

depending on the business logic.


Not quite, the point is that the caller doesn't care how much the loyalty bonus is, only that the bonus it is awarding is for loyalty.

The value of the bonus then gets captured in the domain object. If there is enough related logic to justify having a loyalty bonus object, then you might have

    account.award(LoyaltyBonus.new)
Or, if we're going to start slicing things up like this, we might go for:

    LoyaltyBonus.award_to(account)


How is it more testable than the second option?

  previous = account.balance
  credit_amount = rand 1000
  account.credit_dollars credit_amount
  assert_equal previous + credit_amount, account.balance
And more importantly, how is option 3 a better idea for reuse?


With even a dash of business logic on the transaction object I think 3 is better code.

It's more testable because we can test the concept of a transaction rather than the net result of incrementing the account balance - e.g. test for a business rule to ensure that we can never have a transaction with a negative credit.

It promotes single responsibility because logic pertaining to the transaction can be pushed back onto the transaction object where it is defined once and where it more naturally sits - e.g. validate the transaction or render yourself for printing.

It promotes reuse because the transaction object could possibly be used in another context far removed from this particular scenario - e.g. any logic for validating or rendering the object would sit within the account object otherwise. It makes sense to bring it out where we could potentially reuse it later.

This is just object orientation 101, and it preaches the flexibility that the OP is against. Good OO code is code that is flexible and contains a certain degree of abstractions by design.


But you've lost the abstraction of crediting somebody! Now you have to know about transactions to give out that credit, and while it might be more flexible, you've lost your encapsulation and abstraction.

Option three would be suitable for the implementation of option two, I'll give you that, but if I'm designing an admin control panel I want to bind a control to credit an account, not to create a transaction to credit something and have an account process that transaction.


The point of using a standardized transaction object is that you can than track/manage all such transactions using roughly the same logic. Consider: crediting an account is merely a negative transaction, so there is no point in creating extra overhead or code to manage crediting transactions separately.

This would allow you, for example, to track and handle all transactions, including crediting transactions, using the same code base, with special transaction-type-specific issues dealt with by the transaction-type object.


Reminds me of an old joke: Programmers are good and pacifist people. A good programmer will never write a function like "bombHiroshima". Instead he will write a function "bomb" that takes Hiroshima as a parameter.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: