Tiny Types
Because yes, we hate string-oriented programming.
WHERE ARE WE?
- statically typed
- object-oriented (or at least w/ OO-support)
(Language)
Let's say, Java.
WHERE ARE WE?
- business logic
- domain model representation
(Program context)
Actually can be any program portion (generic libraries, middleware...), but it's usually more useful in these contexts.
WHAT ARE TTYPES?
- class wrappers for native types
- just a value holder class
- w/ equality, hashing
- immutable*
* : or at least with the same mutability constraints of the wrapped type in the used language
LIKE THIS
public class DeviceId {
public final long value;
public DeviceId(long value){
this.value = value;
}
@Override
public boolean equals(Object o) {
if (o instanceof DeviceId == false) {
return false;
}
final DeviceId other = (DeviceId) rhs;
return this.value == other.value;
}
@Override
public int hashCode() {
return Long.hashCode(value);
}
}
THIS HAPPENED
public doSomethingWithIps(SourceDevice s, TargetDevice t)
final long timestamp = now();
final long sourceIpv4Address = s.getAddress();
final long sourceIpv4Mask = s.getNetmask();
final long targetIpv4Address = t.getAddress();
final long targetIpv4Mask = t.getNetmask();
// snip...
final long sourceNetwork = timestamp & sourceIpv4Mask;
return something;
}
Can you spot the error?
AND SO?
Just test it™, right?
CAN DO BETTER
- compiler is your friend
- developer tools like types
RUNTIME COMPILE ERRORS
Plus, need less tests.
public List<LogEntry> loadSomeLogEntriesForDevice(long deviceId, long howMany) {
....
}
final long deviceId = 9999;
final long pageSize = 20;
return loadSomeLogEntriesForDevice(pageSize, deviceId); //woooops!
public List<LogEntry> loadSomeLogEntriesForDevice(DeviceId deviceId, long howMany) {
....
}
final DeviceId deviceId = new DeviceId(9999);
final long pageSize = 20;
return loadSomeLogEntriesForDevice(pageSize, deviceId); //woooops!
this can exist in live system:
this can not (doesn't compile):
SIGNATURES WITH MEANING
Good naming helps even without typing, but it's harder to get wrong here.
BiFunction<Long, Long, Long> ...
BiFunction<Long, Long, Long> ...
BiFunction<Long, Long, Long> ...
BiFunction<Long, Long, Long> ...
BiFunction<DeviceId, Timestamp, ConfigurationId> ...
BiFunction<Latitude, Longitude, Altitude> ...
name the bifunction:
give it a shot now:
getDeviceConfigurationAt
getAltitudeAtLocation
sum
getDeviceConfigurationAt
getAltitudeAtLocation
sum
LESS HEADACHES IN SPRING
@Bean
public BiFunction<Long, Long, Long> getDeviceConfigurationAt(){ ... }
@Bean
public BiFunction<Long, Long, Long> getAltitudeAtLocation(){ ... }
@Bean
public BiFunction<Long, Long, Long> sum(){ ... }
@Bean
public SomethingUsingABiFun something(BiFunction<Long, Long, Long> bf) { ... }
Babum
LESS HEADACHES IN SPRING
@Bean
@Qualifier("this one")
public BiFunction<Long, Long, Long> getDeviceConfigurationAt(){ ... }
@Bean
@Qualifier("not this")
public BiFunction<Long, Long, Long> getAltitudeAtLocation(){ ... }
@Bean
@Qualifier("neither this")
public BiFunction<Long, Long, Long> sum(){ ... }
@Bean
public SomethingUsingABiFun something(
@Qualifier("this one") BiFunction<Long, Long, Long> bf
) { ... }
OK
LESS HEADACHES IN SPRING
@Bean
public BiFunction<DeviceId, Timestamp, ConfigurationId> getDeviceConfigurationAt(){ ... }
@Bean
public BiFunction<Latitude, Longitude, Altitude> getAltitudeAtLocation(){ ... }
@Bean
public BiFunction<Long, Long, Long> sum(){ ... }
@Bean
public SomethingUsingABiFun something(
BiFunction<DeviceId, Timestamp, ConfigurationId> bf
) { ... }
Better
LESS HEADACHES IN SPRING
@Bean
public ConfigurationHistory getDeviceConfigurationAt(){ ... }
@Bean
public TopographicMap getAltitudeAtLocation(){ ... }
@Bean
public BiFunction<Long, Long, Long> sum(){ ... }
@Bean
public SomethingUsingAConfigHistory something(ConfigurationHistory bf) { ... }
Betterest - but this goes a little beyond just wrapping base types.
ENFORCE SEMANTICS
public class Tag {
public final String value;
public Tag(String value){
// a tag cannot contain spaces, that would be two tags!
if(value.contains(" ")){
throw new IllegalArgumentException("invalid");
}
this.value = value;
}
}
public class PersonName {
public final String value;
@Override
public boolean equals(Object o){
// snip null & type checking...
// Doesn't matter if it's written MaRiO or mario,
// we're still talking about Mario!
return value.toLowerCase().equals(other.toLowerCase());
}
}
BEANFORE
public class Device {
public long getId(){ ... }
public long getFirstSeen(){ ... }
public long getLastSeen(){ ... }
public long getNumberOfMeasures(){ ... }
public String getSerialNumber(){ ... }
public String getConfigurationText(){ ... }
}
BEANS AFTER
public class Device {
public DeviceId getId(){ ... }
public Timestamp getFirstSeen(){ ... }
public Timestamp getLastSeen(){ ... }
public long getNumberOfMeasures(){ ... }
public DeviceSerial getSerialNumber(){ ... }
public String getConfigurationText(){ ... }
}
ALL FOR FREE?
CLASS PROLIFERATION
public class DeviceId { ... }
public class ConfigurationId { ... }
public class Tag { ... }
public class Timestamp { ... }
public class Latitude { ... }
public class Longitude { ... }
public class Altitude { ... }
public class DeviceSerial { ... }
public class PersonName { ... }
WHAT ABOUT... HIBERNATE?
public class DeviceId implements EnhancedUserType {
@Override
public abstract int[] sqlTypes(){ ... }
@Override
public abstract Class returnedClass(){ ... }
@Override
public String objectToSQLString(Object value){ ... }
@Override
public Object nullSafeGet(...)
throws HibernateException, SQLException { ... }
@Override
public void nullSafeSet(...)
throws HibernateException, SQLException { ... }
...
}
WHAT ABOUT... JACKSON?
public class DeviceIdSerializer extends JsonSerializer<DeviceId> {
@Override
public void serialize(...)
throws IOException, JsonProcessingException {...}
}
public class DeviceIdKeySerializer extends JsonSerializer<DeviceId> {
@Override
public void serialize(...)
throws IOException, JsonProcessingException {...}
}
public class DeviceIdDeserializer extends JsonDeserializer<DeviceId> {
@Override
public Object deserialize(...)
throws IOException, JsonProcessingException {...}
}
// + the module
WHAT ABOUT... SPRING WEB?
public class StringToDeviceIdConverter implements Converter<String, DeviceId> {
@Override
public DeviceId convert(String source) {...}
}
WHAT ABOUT...
DOZER?
XSTREAM?
ANDROID PARCHELABLE?
WHATEVER ™ ?
TINY RECAP:
+ MEANING
- ERRORS
NOT FOR FREE
SUPPORT LIBRARY MAY HELP
- TESTS NEEDED
THANKS
TinyTypes
By Tsukihara Caligin
TinyTypes
What they are and why use them.
- 2,642