Advantages of FindById().
Future-proofing: If you start with Find(int)
, and later have to add other methods (FindByName(string)
, FindByLegacyId(int)
, FindByCustomerId(int)
, FindByOrderId(int)
, etc), people like me tend to spend ages looking for FindById(int)
. Not really a problem if you can and will change Find(int)
to FindById(int)
once it becomes necessary - future proofing is about these ifs.
Easier to read. Find
is perfectly fine if the call looks like record = Find(customerId);
Yet FindById
is slightly easier for reading if it's record = FindById(AFunction());
.
Consistency. You can consistently apply the FindByX(int)
/ FindByY(int)
pattern everywhere, but Find(int X)
/ Find(int Y)
is not possible because they conflict.
Advantages of Find()
- KISS.
Find
is simple and straightforward, and alongside operator[]
it's one of the 2 most expected function names in this context. (Some popular alternatives being get
, lookup
, or fetch
, depending on context).
- As a rule of thumb, if you have a function name that is a single well-known word which accurately describes what the function does, use it. Even if there is a longer multi-word name that is slightly better at describing what the function does. Example: Length vs NumberOfElements. There is a tradeoff, and where to draw the line is subject of an ongoing debate.
- It's generally good to avoid redundancy. If we look at
FindById(int id)
, we can easily remove redundancy by changing it to Find(int id)
, but there is a trade off - we lose some clarity.
Alternatively you can get the advantages of both by using strongly typed Ids:
CustomerRecord Find(Id<Customer> id)
// Or, depending on local coding standards
CustomerRecord Find(CustomerId id)
Implementation of Id<>
: Strongly typing ID values in C#
Comments here, as well as in the link above, raised multiple concerns regarding Id<Customer>
that I would like to address:
- Concern 1: It's an abuse of generics.
CustomerId
and OrderID
are different types (customerId1 = customerId2;
=> good, customerId1 = orderId1;
=> bad), but their implementation is nearly identical, so we can implement them either with copy paste or with metaprogramming. While there is value in a discussion about either exposing or hiding the generic, metaprogramming is what generics are for.
- Concern 2: It doesn't stop simple mistakes./It's a solution in search of a problem The main issue that's removed by using strongly typed Ids is the wrong argument order in a call to
DoSomething(int customerId, int orderId, int productId)
. Strongly typed Ids also prevent other problems, including the one OP asked about.
- Concern 3: It really just obscures code. It's hard to tell if an id is held in
int aVariable
. It's easy to tell that an Id is held in Id<Customer> aVariable
, and we can even tell that it's a customer Id.
- Concern 4: These Ids are no strong types, just wrappers.
String
is just a wrapper around byte[]
. Wrapping, or encapsulation, is not in conflict with strong typing.
- Concern 5: It's over engineered. Here's the minimal version, although I do recommend adding
operator==
and operator!=
as well, if you don't want to rely exclusively on Equals
:
.
public struct Id<T>: {
private readonly int _value ;
public Id(int value) { _value = value; }
public static explicit operator int(Id<T> id) { return id._value; }
}
T Find<T>(string name)
or(int size)
how do you plan to resolve the inevitable problems?ID
object and not just anint
. In that way get compile-time checking that you do not use an id for an int or viceversa in some part of your code. And with that you can havefind(int value)
andfind(ID id)
.