18 Şubat 2020 Salı

Optional Nasıl Kullanılmalı

Giriş
Optional Anti-Patterns yazısı okunabilir.

Optional Metod Sonucu Olarak Kullanılmalı
Eğer metod sonucu null dönebiliyorsa Optional dönmek daha iyi. 

- Default bir değer dönmek için Optional Nasıl Kullanılmalı - orElse yazısına bakabilirsiniz
- Eğer default değer pahalı bir işlemse yani lazy kullanmak gerekiyorsa Optional Nasıl Kullanılmalı - orElseGet yazısına bakabilirsiniz
- Eğer değer süzülerek döndürülecekse Optional Nasıl Kullanılmalı - Filter yazısına bakabilirsiniz
- Eğer metod sonucu bir Collection ise, Optional dönmek yerine boş Collection dönmek daha iyi

C# İle Karşılaştırma
C# ile out parametre olduğu için şöyle kodlar yazılabiliyor
bool TryGetValue<TKey, TValue>(TKey key, out TValue value)
Aslında artık bu yöntem de pek tercih edilmiyor. Açıklaması şöyle
There are a few reasons this has fallen out of favor. Out parameters require mutability, because you have to create a variable in the calling code that the function mutates. Out parameters are better than reference parameters as they at least explicitly notify the caller that the object will change, but out still has a lot of the downsides associated with passing things by reference. Out parameters also break chaining, which can be acceptable in some cases, but if you make it a pattern in code it makes things harder to follow.

The try get pattern also make it easier to fall into bad practices in code by violating "tell don't ask." Each call is now asking something about it's internal state then deciding to do something based on that state, but all the logic is outside that thing. There are times when this can be acceptable, but it should be avoided as a generally practice. Excessive use of this pattern makes logic harder to follow as objects get used in more places, or bugs to occur when logic gets duplicated.

If the main goal is to avoid null based exceptions, then the better solution is to never let anything be null. This can be achieved with non nullable types and null object patterns. This is also what .Net/C#/Microsoft are starting to encourage with defaulting warnings about possible nulls in new visual studio releases and supporting explicit marking of reference types that may be null
Null Kontrolünden Kurtulmak İçin Kullanılmalı
Yani "handling null"  diyelim. 
Örnek
Normalde şöyle yaparız
//Şu kodda null gelme ihtimali varsa var value = foo.getBar().getBaz().toLowerCase(); //Şöyle yaparız String value = null; if (foo != null) { var bar = foo.getBar(); if (bar != null) { baz = bar.getBaz() if (baz != null) { value = baz.toLowerCase(); } } }
Optional ile şöyle yaparız
var option = Optional.ofNullable(foo) .map(Foo::getBar) .map(Bar::getBaz)     .map(String::toLowerCase);
Örnek
Şöyle yaparız
private String getAssignUserFirstName(Task task) {
  return Optional.ofNullable(task)
    .map(Task::getAssignTeam)
    .map(Team::getManager)
    .map(Manager::getName)
    .orElseThrow(() -> new UnableToGetManagerNameException("..."));
}
Optional Üye Alan Olarak Kullanılmamalı
null olabilecek bir alan için getter() metod Optional dönebilir. Burada dikkat edilmesi gereken nokta, üye alanı Optional yapmak değil. getter() metodun Optional dönmesi. 

Örnek - Optional Serializable Değil
Bunun bir gerekçesi şöyle.
The very important difference is, that Optional is not serializable, so if you created any class with field with Optional you won't be able to serialize it.
Benzer bir gerekçe şöyle.
Java Optional wasn't designed to be used for member variables. There are a number of issues - for example it should have implemented Serializable if it was intended to be used for member variables.
Örnek - Hibernate
Aslında her kütüphane problem çıkartmıyor. Örneğin Jackson Optional tipten üye alanlarla çalışıyor. Ancak bazı kütüphaneler çalışmıyor. Elimizde şöyle bir kod olsun
@Entity
@Table(name = "person")
public class Person {
  @Id
  private long id;

  @Column(name = "firstname")
  private Optional<String> firstName;

  @Column(name = "lastname")
  private Optional<String> lastName;
    
  // constructors, getters, toString, and etc.
}
Bu alana Hibernate itiraz ediyor. Hata şöyle
org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: person, for columns: [org.hibernate.mapping.Column(firstname)]
Örnek - Doğru Kullanım
Açıklaması şöyle.
API Note: Optional is primarily intended for use as a method return type where there is a clear need to represent "no result," and where using null is likely to cause errors.
Şöyle yaparız.
private final String addressLine;  // never null
private final String city;         // never null
private final String postcode;     // optional, thus may be null

// normal getters
public String getAddressLine() { return addressLine; }
public String getCity() { return city; }

// special getter for optional field
public Optional<String> getPostcode() {
  return Optional.ofNullable(postcode);
}
Optional Method Argument Olarak Kullanılmamalı
Bu konuyla ilgili aslında tam bir uzlaşma yok. Bazılarına göre bu durum kabul edilebilir.
Optional method argument olmamalı diyenlerin açıklaması şöyle. Yani "Never use Optional as a method argument" diyenler. Bunlara Taraf 1 diyelim
Optional is not a replacement for null nor a fancy way to prevent NullPointerException. It is to indicate that the question is unanswerable, like: what is the average age of an empty list of persons.

Optionals should never be passed on, but unboxed by the calling code as soon as possible.
Taraf 1'in açıklaması şöyle. Yani null değer istemiyorsak @NonNull kullanarak zaten bunu yakalayabiliriz.
While writing a method it is recommended to annotate the required argument with @NonNull . Since author of the code knows which argument is required and which not . So better to use basic validation and throw exception in case of validation failed.
Optional method argument  olabilir diyenlerin açıklaması şöyle. Bunlara Taraf 2 diyelim.
Passing an Optional result to another method, without any semantic analysis; leaving that to the method, is quite alright.
Optional method argument olmamalı diyenler şöyle kodlar olmamalı diyorlar.
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}
Çözüm 1 - Optional Parametreden Kurtulmak İçin Overload metod kullanmak
Aşağıdaki örneklerde her iki tarafın da haklı olduğu örnekler verdim.

Örnek - Tek parametre null olabiliyorsa
Eğer Optional kullansaydık şöyle yapardık
public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}
Taraf 1'e uyarak Optional parametreden kurtulmak için metodumuzu overload ederek şöyle yapalım.
public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}
Overload edilen metodun birinci halini çağırmak için şöyle yaparız
SystemMessage withoutAttachment = new SystemMessage("title", "content");
Overload edilen metodun ikinci halini çağırmak için şöyle yaparız
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

Örnek - İki parametre null olabiliyorsa
Bu örnekte aslında Optional parametre geçmemenin kodu daha da zorlaştırdığı görülüyor.
Optional kullansaydık şöyle yapardık.
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}
Taraf 1'e uyarak Optional parametreden kurtulmak için metodumuzu overload ederek  şöyle yapalım. Burada null olabilecek parametreler için metod overload ediliyor.
public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}
Bu durumda kodu çağırmak için şöyle yaparız. Görüldüğü gibi kod daha karmaşık hale gelmeye başladı
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}
Çözüm 2 - Optional Parametreden Kurtulmak İçin Metod Çağrısında Optional.orElse Kullanmak
Örnek
Metodumuzdaki Optional parametreleri kaldırarak şöyle yaparız. Metoda null parametre gelecekmiş gibi kabul edip, kontrol etmek gerekir.
public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}
Bu durumda kullanmak için şöyle yaparız.
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
Örnek
Elimizde şöyle bir kod olsun
public void doAction() {
  OptionalInt age = getAge();
  Optional<Role> role = getRole();
  applySettings(name, age, role);
}
Metod imzasında Optional parametre kullanmamaya karar verdiysek şöyle yaparız. Burada default değerler kullanılıyor.
public void doAction() {
OptionalInt age = getAge(); Optional<Role> role = getRole(); applySettings(name, age.orElse(defaultAge), role.orElse(defaultRole)); }




Hiç yorum yok:

Yorum Gönder