Prinsip SOLID Dalam Software Engineering

SOLID Principle

Sedikit catatan 🚨

Prinsip SOLID ini sedikit banyak berkaitan dengan OOP atau Object Oriented Programming. So, semisal teman-teman berangkat dari fungsional programming seperti:

  • R
  • F#
  • Erlang
  • Clojure
  • Dan lainnya

Bisa skip, atau tetap dibaca untuk pengetahuan juga boleh 😄


Dalam dunia pemrograman perangkat lunak atau software engineering, seringkali kita mengabaikan hal-hal prinsip yang seharusnya dikuasai, biasanya kita berfikiran pokok sudah selesai ngetik kode, kemudian dijalankan bisa yasudah berarti sudah selesai tugas kita sebagai programmer (Sayapun dulu, bahkan sekarang kadang begitu 😀).

Memang tidak salah sih, cuman acapkali ketika kita me-maintain, me-revisit, atau melihat kode kita dalam beberapa minggu atau bahkan bulan akan menjadi bingung karena kode function yang banyak sekali terduplikat, function yang terlalu besar sehingga sulit dibaca, modif class A tapi yang lain kesenggol, class child yang tidak bisa selayaknya parent class, interface yang "memaksa" sebuah class padahal tidak butuh dan lain sebagainya, yang pada akhirnya akan menyebabkan bug yang merepotkan di kemudian hari.

Nah, untuk itu, daripada kita sibuk untuk mencari solusi dan prinsip baru lagi "sendiri", yang dimana sebenernya sudah banyak yang mengalami hal tersebut dan mendapatkan solusi yang proven serta sudah banyak diadopsi luas. Maka mari kita berkenalan dengan SOLID Principle.

⚓ Adapun poin dari prinsip SOLID ini adalah:

  • SRP - Single Responsibility Principle.
  • OCP - The Open Close Principle.
  • LSP - The Liskov Subtitution Principle.
  • ISP - The Interface Segregation Principle.
  • DIP - The Depedency Inversion Principle.

Okee, let's Goo !! 🛫

SRP - Single Responsibility Principle 🍉

Kalo dibahasa indonesiakan berarti prinsip tanggungjawab sendiri 😂. Inti dari prinsip ini adalah, sebuah class yang memiliki banyak tugas/fungsi konteks maka akan meningkatkan peluang terjadinya bugs. Karena ketika solving bugs/change request di class tersebut acapkali kita tidak aware "menyenggol" fungsi lainnya.

Sebentar sebentar, lah terus apa gunanya pake class yhak? kalo gitu aja pake function aja juga bisa, kalo emang cuman 1 class punya 1 function 😳

Begini kisanak, yang perlu di garis bawahi disini adalah konteksnya. Yang dimaksud diatas adalah 1 class harus mengandung 1 konteks yang sama, bukan literally harus 1 method saja. Begini deh, coba kita breakdown di kodingan langsung yah. Untuk contoh dibawah menggunakan Typescript, harusnya di bahasa lain tidak berbeda jauh 😀

class TextMaker {
  private _text: string;

  public set setText(text: string): void{
    this._text = text;
  }

  public get getText(): string{
    return _text;
  }

  public appendText(textToAppend: string): string{
    // Logic here...
  }

  public deleteText(word: string): string{
    // Logic here...
  }

  public printInSentence(input: string): void{
    // Logic here...
  }

  public printInChar(input: string): void{
    // Logic here...
  }
}

Coba, dari kode diatas mana yang menyalahi prinsip SRP?
Apakah kalian menemukannya?
Dimana?
Teriak lebih keras! -Mode Dora ~

Yak betul, method printInSentence() dan printInChar() !! class tersebut memiliki konteks campur antara memodifikasi text dan menampilkan text !! Nah, untuk memperbaiki sesuai dengan prinsip, maka class tersebut harus direfactor menjadi 2 class sesuai dengan konteks dan responsibility nya masing-masing:

class TextMaker {
  private _text: string;

  public set setText(text: string): void{
    this._text = text;
  }

  public get getText(): string{
    return _text;
  }

  public appendText(textToAppend: string): string{
    // Logic here...
  }

  public deleteText(word: string): string{
    // Logic here...
  }
}
class TextPrinter {
  public printInSentence(input: string): void{
    // Logic here...
  }

  public printInChar(input: string): void{
    // Logic here...
  }
}

OCP - The Open Close Principle 🍋

"Entitas perangkat lunak(class, modules, methods, etc) harus terbuka untuk extension, tapi harus tertutup terhadap modifikasi dari luar." ☝️

Inti dari prinsip ini adalah ketika terjadi penambahan suatu objek baru, maka objek lama yang di extends seharusnya tidak berubah secara besar. Contoh implementasinya adalah penggunaan extends dan implement seperti di bawah ini.

class Shapes {
  public area(shape: Shapes): number{
    let area:number = 0,0;

    if (shape instanceof Rectangle){
      const rectangle: Rectangle = (Rectangle)shape;
      area = rectangle.length*rectangle.breadth;
    } else if (shape instanceof Circle){
      const circle: Circle = (Circle)shape;
      area = 3.14*circle.radius*circle.radius;
    }

    return area;
  }
}

class Rectangle extends Shapes {
  const length:number;
  const breadth:number;
}

class Circle extends Shapes {
  const radius:number;
}

Apakah ada yang salah? Tidak !! apakah bisa menghitung sesuai ekspektasi? Bisa !! terus apa yang salah? 🤔

Yak, yang salah disini adalah ketika kita menambahkan bangun datar baru. Setiap kita menambahkan yang baru, maka kita juga harus merubah seleksi kondisi perhitungan di parent classnya, bagaimana? sudah tergambarkan keribetannya? mungkin karena masih sedikit jadi masih gampang. Jika sudah ada ribuan line code, maka disitulah kesulitan terjadi 🤯

Solusi dari masalah tersebut adalah merefactor class Shapes menjadi interface !!

class interface Shapes {
  abstract area(): number;
}

class Rectangle implements Shapes {
  const length:number;
  const breadth:number;

  public override area():number{
    return length*breadth;
  }
}

class Circle implements Shapes {
  const radius:number;

  public override area():number{
    return 3.14*radius*radius;
  }
}

LSP - The Liskov Subtitution Principle 🍓

  • To Be Continue ~

About the author

Hey, I'm Ubaidillah Hakim Fadly, a software engineer, stock investor, and entrepreneur. I publish articles and tutorials about programming and devOps from my own research.

Get the newsletterBuy me a coffee

Ubaidillah