Alias say hello to Flow!

Ada beberapa yang mempermasalahkan dynamic typing, terlebih saya. Jika kalian berasal dari Java, Objective-C, atau Haskell, pasti sudah familiar dengan apa yang kita tulis, adalah apa yang kita inginkan. Jika kita membuat sebuah fungsi/methods yang nilai keluaran nya String, maka pasti akan String. Jika bukan, akan error di compile time.

Misal contoh, hanya contoh, kita membuat methods untuk mengecek apakah nilai tersebut adalah sebuah number.

function checkIsNumber(number) {
  return typeof parseInt(number, 10) === 'number'
}

Disitu, saya sudah menggunakan komparasi berdasarkan reference bukan value. Yang berarti, entah saya memanggilnya seperti ini: checkIsNumber('2'), seperti ini: checkIsNumber(13.37), dan pasti nya seperti ini: checkIsNumber(3), keluaran nya akan selalu true.

Tapi ada masalah disini. Bagaimana bila parameter yang diberikan bernilai Boolean? Atau undefined? Atau null? Tentu akan true, karena NaN adalah number.

Kita menggunakan methods parseInt untuk meng-handle bila ternyata developer mengirim parameter ber-tipe String/Float. Tentu bisa kita selesaikan dengan mengecek terlebih dahulu, baru kita panggil parseInt jika memang String/Float, tapi kesalahan ini terjadi disisi developer, dan setiap baris kode memiliki cost :)

Typing System for the rescue

Akhirnya JavaScript memiliki typing system! Pada 2012 JavaScript memiliki typing system via TypeScript. Singkatnya, TypeScript adalah JavaScript dengan typing system. Yang berarti, JavaScript adalah TypeScript tanpa typing system.

TypeScript dikembangkan oleh tim Microsoft, dan Open Source. Ada beberapa tantangan yang harus diambil selaku JavaScript Engineer: Mempelajari syntax TypeScript. Banyak hal yang "bukan JavaScript" harus dipelajari. Seperti access modifier, abstract classes, modules (konsep nya beda dengan NodeJS), namespace, dll.

Meskipun enggak perlu dipelajari semua.

Browser dan NodeJS (v8) hanya mengerti JavaScript. Berarti, TypeScript akan meng-transpile kode tersebut ke JavaScript. Dan pasti nya membutuhkan waktu. Dan enggak semua developer ingin mempelajari TypeScript, dengan alasan Developer Experience.

Lalu pada tahun 2014, tim dari Facebook merelease secara public kode Flow. Yang menarik dari flow adalah kalian benar-benar menambahkan typing ke code JavaScript kalian, bukan menggunakan bahasa program lain. Yang berarti, kalian bisa menambahkan/tidak typing system di kode kalian sesuka kalian.

Dan karena si browser/v8 cuma ngerti JavaScript, kita harus mengubah kode tersebut menjadi JavaScript. Bukan di-transpile, tapi di-remove. Kalau enggak mau di-remove, kalian bisa menggunakan typing system di flow, via komentar.

// @flow

const thisIsNumber: number = 2
const thisIsNumberToo /*: number */ = 2

Dua baris kode tersebut merupakan deklarasi yang sama, dengan cara berbeda. Akan ada error bila nilai dari variable tersebut ber-tipe selain number.

Kenapa Flow bukan TypeScript?

Setiap sesuatu pasti ada kekurangan dan kelebihan. Di TypeScript, kalian bisa menggunakan VSCode sebagai "IDE". Kalian tidak perlu instalasi plugin apapun untuk bisa meng-integrasi syntax TypeScript. Dari type checking, Go to Definition, sampai code suggestion/completion.

Dan juga, kalian tidak perlu menambahkan module ke devDependencies kalian. Just run out of the box.

Beda dengan flow, kita perlu plugin khusus di editor kita. Belum ada editor yang default mendukung syntax flow. Dan juga kita perlu menambahkan module flow ke devDependencies kita agar flow bisa berjalan dengan lancar. Jika kalian hanya ingin mengecek code kalian di compile-time, kalian bisa menginstall flow di global environment shell kalian, dan eksekusi flow. Jika ada error/warning, silahkan fix sendiri berdasarkan pesan yang muncul.

Alasan saya adalah agar menjaga pengalaman saya dalam menulis kode JavaScript. Yaa contoh seperti Null dan Object, yang mana:

const numberOne: Object = 1 // works in ts
const numberTwo: Object = 2 // error in flow

Karena di flow, null merupakan sebuah type sendiri. Jadi, jika kalian membutuhkan parameter di sebuah methods yang 'berpotensi' bernilai null, maka kalian bisa menggunakan null sebagai type dari parameter tersebut, alias nullable.

Dan juga, enggak ada konfigurasi ribet-ribet. Kalau kalian menulis kode kalian menggunakan ES6+, kalian membutuhkan plugin flow di Babel kalian, dan jika kalian menulis kode di ES5-, kalian bisa tinggal gunakan flow-remove-types yang berguna sesuai dengan nama package nya.

Mari mulai.

Instalasi

Proses nya gampang, tinggal tambahkan flow ke devDependencies kalian.

npm install flow --save-dev

Jika kalian menggunakan babel, tinggal tambahkan babel-preset-flow, dan jika tidak, bisa gunakan flow-remove-types dan install di global.

Untuk memberitahu editor/transpiler bahwa kode kalian menggunakan flow, tambahkan @flow sebagai komentar dan tempatkan dipaling atas kode kalian.

// @flow

class App {
  // code
}

Primitive Type

Ada beberapa primitive type di JavaScript:

  1. Boolean
  2. String
  3. Number
  4. null
  5. undefined
  6. Symbol (Belum ter-support di Flow)

Untuk memberitahu transpiler bahwa variable/methods tersebut ber-tipe primitif, kalian bisa menambahkan : setelah nama variable/methods

const zero: number = 0

function capitalize(text: string): string {
  return text.charAt(0).toUpperCase() + text.slice(1)
}

const isOnline: boolean = true

capitalize("fariz") // Fariz

// Error

capitalize(true)
           ^ Cannot call `capitalize` with `true` bound to `text` because boolean [1] is incompatible with string [2].

Kadang ada bingung kapan menggunakan String daripada string, atau Number daripada number. Gunakan "lower-cased" type jika literal value, bukan dari constructor. Jadi:

const myName: String = "Fariz" // error
const myNameAgain: String = new String("Fariz") // works

const isTodayMonday: Boolean = false // error
const isTodayIsMonday: Boolean = new Boolean(false) // works

const justUserThis: string = "okay" // works, because not created from constructor

Just for info, jarang banget yang menggunakan Object wrapper.

Class Types

Semakin banyak variable yang dideklarasikan, semakin banyak pula potensi ada nya bug. Kalian enggak bisa sembarangan membuat member variable. Misal kalian ingin membuat class Mahasiswa dengan member variable nama dan nim.

class Mahasiswa {
  constructor(nama: string, nim: number) {
    this.nama = nama
    this.nim = Mahasiswa._storeNim(nim)
  }
  
  static _storeNim (nim: number): string {
    const censoredNim = nim.toString().substr(0, 4) + 'xxx'
    Mahasiswa._nim = nim
    return censoredNim
  }
  
}

Sekilas kode diatas tidak ada error, tapi enggak di flow.

this.nama = nama
     ^ Cannot assign `nama` to `this.nama` because property `nama` is missing in `Mahasiswa` [1].

this.nim = Mahasiswa._storeNim(nim)
     ^ Cannot assign `Mahasiswa._storeNim(...)` to `this.nim` because property `nim` is missing in `Mahasiswa` [1].

Mahasiswa._nim = nim
          ^ Cannot assign `nim` to `Mahasiswa._nim` because property `_nim` is missing in statics of `Mahasiswa` [1].

Kita harus mendeklarasikan property yang akan digunakan di class kita, berikut dengan type nya.

class Mahasiswa {
  static _nim: number
 
  nama: string
  nim: string
  
  constructor(nama: string, nim: number) {
    this.nama = nama
    this.nim = Mahasiswa._storeNim(nim)
  }
  
  static _storeNim (nim: number): string {
    const censoredNim = nim.toString().substr(0, 4) + 'xxx'
    Mahasiswa._nim = nim
    return censoredNim
  }
}

Goodbye error.

Selebihnya tentang Type Annotations apa saja yang bisa diterapkan, dan bagaimana, bisa di cek disini

Mengapa Typing System penting?

Dulu sering banget validasi yang sifat nya developer mistakes. Yang artinya bukan berada di level app logic. Terlebih di project yang sudah lumayan besar ya. Saya sering memanggil parseInt() cuma untuk memastikan nilai yang saya komparasi ini benar-benar sebuah number, karena saya selalu menggunakan === alias comparison by reference.

Self documentation. JSDoc is great, but writing clean code is awesome. Bandingkan kedua kode ini:

/**
 * Function to truncate text
 * @param {string} - Text to truncate, required.
 * @param {number} - Number of max chars, default: 140
 * @param {string} - Truncate symbol, default: '...'
 * @return {string} - Truncated text
*/
 
function truncateText(text, maxLength=140, symbol='...') {
  if (typeof text !== 'string') {
    throw new Error('Text must string')
  }
  return text.length > maxLength ? text.substr(0, maxLength) + symbol : text
}

Dengan ini:

function truncateText(text: string, maxLength: number = 140, symbol: string = '...'): string {
  return text.length > maxLength ? text.substr(0, maxLength) + symbol : text
}

Dan akan mengeluarkan hasil yang sama, tanpa perlu mengecek apakah parameter text ber-tipe string/bukan, dan semoga kode ini mudah dibaca, sehingga developer tau apa yang terjadi dengan method yang saya buat diatas.

Works well with VSCode! Dan ya, di VSCode saya men-set: javascript.validate.enable = false, karena VSCode tidak mengerti syntax Flow, dan saya selalu menggunakan ESLint & StandardJS di Module Bundler, jadi jika ada error terkait linting, maka muncul error nya di browser, dan btw error ini jarang terjadi haha.

Summary

Intinya, saya sudah tidak terlalu pusing menulis kode di JavaScript. Mungkin untuk project skala kecil dan dihandle sendiri/hanya beberapa orang belum terasa, namun bila project sudah lumayan besar, sudah banyak methods yang kita buat, tentu ini lumayan efektif.

Terlebih, flow (dan plugin di VSCode) memiliki fitur intellisense juga, jadi ketika kita mengetik sesuatu, maka akan ada code suggestion, berikut dengan nama variable/methods, tipe nya, dan informasi (misal seperti apakah parameter ini opsional/enggak).

Tidak perlu untuk validasi apakah ini string/bukan, number/bukan, dll. Kalau jenis nya input dari user, yaa pastinya harus validasi dong. Ya, never trust user input.

Dan juga, belajar menulis kode yang rapih dan belajar membaca kode yang ada. Kadang kalau coding hari Jum'at, pas hari Senin udah lupa, ini gue buat fungsi ini buat apaan dah, lah variable ini buat apaan ya? Comment segan, hapus tak mau. Akhirnya kasih label // FIXME: Why this happen atau // TODO.

Atau itu salah satu nya. Biar enggak ada masalah, terlebih karena ehm, kode lebih dari 880+ baris (btw, Vue Single File Component), jadi mending di komentar dulu. Kalau ada waktu baru refactor, karena baru nulis testing di library aja.

Jadi begitu kisah tentang Typing System, menggunakan Flow gampang sekali di-integrasi dengan framework apapun, karena tinggal: Hapus flow typing di kode JavaScript lo. .ts or .tsx are not required.