Please critique this CoffeeScript code. Is it idiomatic?
MoneyAccount = (balance = 0) ->
obj = {
withdraw: (x) ->
if balance < x
throw new Error("not enough funds in account")
else
balance -= x
console.log "withdrew funds."
x
deposit: (x) ->
if (typeof x isnt 'number' || x < 0)
throw new Error("bad deposit amount " + x)
else
balance += x
printBalance: ->
console.log balance
}
obj
1 Answer 1
This seems closely tied to your other question. I posted an answer for that one, which I'll incorporate/reference here.
Calling it MoneyAccount
(in "UpperCamelCase") implies that it's a constructor. But it's not quite; it's just a function. But I assume that's done on purpose in order to have "private" variables (i.e. the closed-over balance
argument) while still allowing one to invoke it with or without the new
keyword.
Still, there are a couple of things you could do:
You're diligent in checking the input for
deposit
andwithdraw
... but not the initial balance. Soaccount = MoneyAccount("foo")
would spell trouble right away.
But even if you check the type of the initial balance, it'd still allow someone input a negative initial balance. But the waywithdraw
works seems to imply that a negative balance is impossible.
Incidentally, I could also sayaccount = MoneyAccount(1/0)
and haveInfinity
money! Sounds nice, but perhaps not what you want.Similar to the above, there's nothing stopping you from withdrawing or depositing a negative or infinite amount.
As mentioned in the answer I linked to, you don't need
if...else
branches if you throw an an exception; the function will exit if it throws.There's no way to just get the balance, which would seem problematic for an account. You can print it, sure, but you can't just get it. If you add a method to do that, you can skip the
console.log
stuff (which incidentally, you're only doing forwithdraw
but not fordeposit
). Logging stuff isn't really the responsibility of an account object anyway; leave that to external code.x
should probably be namedamount
instead, just to be descriptive.You might want to throw a
RangeError
inwithdraw
to be a bit more precise.Return values are a little haphazard.
deposit
returns the new balance whilewithdraw
returns the amount withdrawn.The
obj
var is unnecessary; you can just leave it out entirely. And you can even leave out the curly brackets (which I personally like to do in a case like this, but it's a matter to taste, really).
In the end, I get this:
MoneyAccount = (balance = 0) ->
# Adapted from the linked answer
assertValidAmount = (amount) ->
number = Number amount
if isNaN(number)
throw new TypeError("Expected a numeric amount")
if not isFinite(number) or number < 0
throw new RangeError("Expected a positive, finite amount")
assertValidAmount balance
# everything below will be interpreted as an object
# and returned, even without the curly braces
balance: -> balance
withdraw: (amount) ->
assertValidAmount amount
throw new RangeError("Insufficient funds") if amount > balance
balance -= amount
deposit: (amount) ->
assertValidAmount amount
balance += amount
That should be pretty safe.
-
\$\begingroup\$ I used the Function rather than
class
due to CoffeeScript Ristretto's examples. With respect toIf it's not on purpose, and you'd be ok with having balance be publicly accessible
, how can that ever be acceptable? Thanks for your informative, detailed answer. I appreciate it. \$\endgroup\$Kevin Meredith– Kevin Meredith2014年05月25日 01:17:41 +00:00Commented May 25, 2014 at 1:17 -
\$\begingroup\$ @KevinMeredith I agree that a publicly accessible
balance
sounds like a bad idea - I just mentioned it because that'd be the consequence of using CoffeeScript'sclass
. But in the end, JS doesn't have a built-in concept of private/public visibility or accessibility (e.g. you can redefine the functions and properties on existing prototypes), so it's often times easier to just leave things loose than erect and maintain a bunch of "artificial" barriers (similar to GIGO vs exceptions). Closed-over "private" vars are however simple to do, so that's encouraged! \$\endgroup\$Flambino– Flambino2014年05月25日 01:40:31 +00:00Commented May 25, 2014 at 1:40 -
\$\begingroup\$ If
class MoneyAccount
were used instead, then couldn't we always use anormal variable
like so:class Queue foo = "Hey there" constructor: -> ...
? When constructing a new object,foo
wouldn't be accessible from without theclass MoneyAccount
, if I understand correctly. Example - gist.github.com/kman007us/18dd99f8962c88a874ae \$\endgroup\$Kevin Meredith– Kevin Meredith2014年05月25日 03:37:22 +00:00Commented May 25, 2014 at 3:37 -
\$\begingroup\$ @KevinMeredith Ah, of course, you are correct. My mistake; I was misremembering the syntax. I've edited the answer. Thanks for the correction \$\endgroup\$Flambino– Flambino2014年05月25日 03:49:43 +00:00Commented May 25, 2014 at 3:49