Mufid's Code blog

Beberapa Strategi Pengembangan dalam Membangun Perusahaan Rintisan Digital

| Comments

Membuat perusahaan rintisan tidak lagi sebuah tren. Anda dapat membangun perusahaan Anda sendiri dengan modal kurang dari lima belas juta rupiah saja. DNA dari sebuah perusahaan digital adalah perangkat lunak itu sendiri. Selama Saya membantu membangun [Peentar](https://peentar.id) dari awal, saya mempelajari banyak sekali strategi membangun perangkat lunak dan mencobanya. Dalam tulisan ini, saya akan memaparkan strategi-strategi yang pernah kami coba. ## 1. Membangun Arahan Perusahaan dan Perencanaan Keuangan Dalam perusahaan manapun, keuangan merupakan hal yang utama. Ada dua strategi dalam perencanaan ini: mulai dari arahan dulu, atau mulai dari keuangan dulu. Semisal, arahan sudah ditentukan yaitu membuat MVP di 6 bulan pertama. Setelah kita mengetahui arahan, kita dapat mengetahui ## 2. Pembangunan ### A. Membuat dari awal ### B. Membuat dari solusi perangkat lunak sumber terbuka ### C. Membuat perangkat lunak sumber terbuka ### D. Menyerahkan ke Vendor ## 3. Maintenance

Mendapatkan Surel (Email) Phising

| Comments

Ini pertama kalinya saya mendapatkan surel Phising. Surel Phising ini sendiri adalah sebuah surel yang mirip seolah-olah seperti surel asli dengan tujuan mencuri data pengguna. Kalau di SMS, ini mirip seperti “SELAMAT! NOMOR ANDA MENDAPAT HADIAH.” Padahal hadiahnya tidak ada.

Phising 1 Phising 2 Phising 3

Thermofidz: Membaca Temperatur dengan Raspberry PI

| Comments

Latar Belakang

Pada kesempatan kali ini saya iseng luar biasa ingin membuat termometer yang tersambung ke cloud. Istilah kerennya, “Internet of Things.” Ide ini muncul karena akhir-akhir ini suhu udara tidak menentu. Terkadang saya gerah sekali ketika malam hari. Saya memiliki termometer “offline” yang selalu memberi tahu saya saat ini suhu udaranya berapa. Saya memiliki hipotesis, saya akan sangat gerah dan berkeringat luar biasa jika suhu udara di atas 32 derajat celcius dan saya akan merasa sejuk jika suhu udara di bawah 28 derajat celcius.

Untuk memvalidasi hipotesis saya, tentu saja saya perlu memiliki catatan berapa suhu saat ini pada setiap waktu. Sayang sekali, termometer offline seharga Rp30.000an ini tidak memiliki catatan. Andai ada sebuah termometer yang memiliki catatan dan disimpan ke berkas, itu akan sangat membantu. Akhirnya tercetuslah ide membuat termometer sendiri.

Peralatan

Saya mengikuti tutorial dari Adafruit mengenai cara membaca temperatur dengan Raspberry. Di tutorial tersebut, mereka menggunakan sensor DS18B20. Saya segera mencari sensor tersebut di Bukalapak. di sana kita akan menemukan banyak produk, tetapi banyak pelapak yang tidak aktif juga. Saya kemarin membeli di lapak yang ini. Dan iya, harga sensornya Rp30.000an — sudah sama mahalnya dengan termometer offline yang saya beli.

Kemudian saya membeli breadboard di toko elektronik dekat kantor. Harganya Rp27.000. Saya juga membeli satu paket resistor Sparksfun di Famosa Studio. Dengan peralatan yang sudah siap di tangan, saya bisa mengikuti tutorial dari Adafruit. Skematik yang diberikan cukup straightforward. Tada, ini dia hasilnya.

!!!!!!!!!!!!!!!!!! photos goes here

Konfigurasi Raspberry

Selanjutnya adalah menyiapkan Raspberry. Saat itu saya mencoba dengan Raspberry generasi awal tipe B dengan memori 256 MB. Kemudian agar tidak lambat updatenya, saya memasang Raspbian versi terbaru ke SD Card dengan Win32DiskImager. Begitu saya tancap kabel power, Raspberry tidak tersambung ke jaringan. Bahkan dia tidak hidup, hanya ada 1 LED merah yang menyarah (power) dabntidak ada tanda-tanda kehidupan. Ups.

Penasaran, saya pindahkan SD Card ke Raspberry PI saya yang satu lagi yang berukuran 512 MB. Hidup! Ada aktivitas disk! Ada aktivitas jaringan, tetapi gagal saya ssh. Ups. Saya coba login manual dengan menancapkan kabel HDMI ke monitor dan menancapkan kabel keyboard USB. Usut punya usut, ternyata SSH daemon harus diaktifkan secara manual. Oh. Oke, perkara sederhana.

Meski demikian, saya masih penasaran mengapa versi terbaru tidak jalan di model 256 MB. Oleh karenanya saya coba memasang versi yang berbeda di SD Card dan mencoba menancapkannya di masing-masing Raspberry yang saya miliki. Berikut hasilnya:

Model/Konfigurasi 2016-03-18-raspbian-jessie-lite 2017-01-11-raspbian-jessie-lite
Raspbery PI B (Gen. 1) 256 MB Jalan Tidak mau hidup, layar boot tidak muncul
Raspbery PI B (Gen. 1) 512 MB Jalan Jalan

Hmmmm. Cukup. Aneh. Entah saya yang terburu-buru, atau memang ada galat di versi terbaru. Saya tidak yakin yang terakhir. Tapi saya sudah melakukan rewrite berulang-ulang ke SD Card saya dan hasilnya sama saja. Oke, kita settle-kan saja dengan yang 512 MB dulu.

Tutorial dari Adafruit cukup straightforward. Akan tetapi script Python nya terlalu panjang untuk saya. Saya akhirnya membuat Bash Script sederhana seperti ini:

$ cat worker.sh
#!/bin/bash

TARGET=$(find /sys/devices/w1_bus_master1/28* | grep w1_slave)
TEMP=$(cat $TARGET | grep -o 't=.*')
echo "Temperature reading is: $TEMP"

Mari kita lihat hasilnya

$ bash worker.sh
Temperature reading is: t=29250

Woow berjalan dengan sempurna! Baiklah, seharusnya, saya tinggal membuat Cronjob kemudian menyimpan hasilnya ke berkas file. Jadilah sebuah pencatat temperatur. Kendati demikian, saya ingin membuat hal yang lebih menarik yaitu menyambungkan perangkat ini ke Cloud. Oleh karena saya malas membuat basis data yang kompleks, saya memilih untuk menggunakan Git saja.

Sementara, hasil bacaan dari temperatur dapat dilihat pada akun Github saya:

mufid/thermofidz

Btw, ini hasil rangkaiannya. Tidak jauh berbeda dengan yang ditunjukkan oleh Adafruit:

Hasil Utak Atik

Yak, begitulah.

Benchmarking ActiveRecord Load: #find_in_batches (or #find_each) and #each For Processing Bulk Records

| Comments

Currently i am building a small enough Rails application, but somehow on some endpoints, we process and iterate 10k records on a single request. Sure i am caching all of those requests, but 870ms response time for first request is still unacceptable for me.

Some says it is problem with the JSON rendering. Then i installed oj gem, but the request didn’t get a noticable faster rendering. And then i deleted all JSON rendering, the request still on 800-ish ms response time. All of these findings suggest me that the problem is in the model / query itself.

When processing much records, it would be very easy to get out of memory problem if it is being done incorrectly. By default, when using #each method, Rails will load all records with specified filters (where, scope, etc) into memory. Thus, Rails’ recommendation is to use find_each command, which internally using find_in_batches method. find_in_batches will not try to load all matched records in memory. Instead, it will load records by batch.

The thing is, i was using find_each, but somehow, i was stuck on 2 seconds request. I think the problem was the find_in_batches so i changed the implementation to use find_in_batches. However, this way didn’t help me. I still got 800-ish ms response time.

Somehow, i was curious, was the problem is inside the find_in_batches? Or is it in the Ruby yield and loop? I am not sure since computer nowadays have gigahertz of computing power. However, i did the benchmark and the benchmark looks like this:

class Metadata < ApplicationRecord
  def build_summary
    points = Metadata.where('retrieved_at > \'2000-01-01 00:00:00\'')

    logger.info 'Benchmark: #find_in_batches'
    logger.info begin
      Benchmark.measure do
        points.find_in_batches do |dp|
          logger.info "End finding batches. Found: #{dp.count}"
        end
      end
    end

    logger.info 'Benchmark: #all'
    logger.info begin
      Benchmark.measure do
        points.each do |dp|
          # yield nothing
        end
      end
    end

    logger.info 'Benchmark: 10k loop'
    logger.info begin
      Benchmark.measure do
        (1..10_000).each do |i|
          # yield no one
        end
      end
    end
  end
end

I added ‘normal’ loop, just curious if the problem is inside the Ruby’s interpreter.

Here is the result. I did this on Dual Core 4 GB RAM Virtualbox inside Intel Core i5 with 16 GB RAM. I used Ruby 2.3.1 and Rails 5.0.0.1. For database, i use PostgreSQL 9.6.

I, [2016-12-15T14:56:37.463516 #28376]  INFO -- : Benchmark: #find_in_batches
I, [2016-12-15T14:56:37.511837 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.516353 #28376] DEBUG -- :   Metadata Load (3.2ms)
I, [2016-12-15T14:56:37.550252 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.553774 #28376] DEBUG -- :   Metadata Load (2.8ms)
I, [2016-12-15T14:56:37.596984 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.603011 #28376] DEBUG -- :   Metadata Load (5.4ms)
I, [2016-12-15T14:56:37.645731 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.652892 #28376] DEBUG -- :   Metadata Load (6.5ms)
I, [2016-12-15T14:56:37.692978 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.696923 #28376] DEBUG -- :   Metadata Load (3.1ms)
I, [2016-12-15T14:56:37.738265 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.742807 #28376] DEBUG -- :   Metadata Load (3.5ms)
I, [2016-12-15T14:56:37.787560 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.792086 #28376] DEBUG -- :   Metadata Load (3.1ms)
I, [2016-12-15T14:56:37.828058 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.836220 #28376] DEBUG -- :   Metadata Load (7.2ms)
I, [2016-12-15T14:56:37.907940 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.911302 #28376] DEBUG -- :   Metadata Load (2.8ms)
I, [2016-12-15T14:56:37.943847 #28376]  INFO -- : End finding batches. Found: 1000
D, [2016-12-15T14:56:37.945335 #28376] DEBUG -- :   Metadata Load (0.9ms)
I, [2016-12-15T14:56:37.949258 #28376]  INFO -- : End finding batches. Found: 157
I, [2016-12-15T14:56:37.949421 #28376]  INFO -- :   0.330000   0.010000   0.340000 (  0.485759)
I, [2016-12-15T14:56:37.949613 #28376]  INFO -- : Benchmark: #all
D, [2016-12-15T14:56:37.991020 #28376] DEBUG -- :   Metadata Load (40.9ms)
I, [2016-12-15T14:56:38.218292 #28376]  INFO -- :   0.220000   0.000000   0.220000 (  0.268597)
I, [2016-12-15T14:56:38.218840 #28376]  INFO -- : Benchmark: 10k loop
I, [2016-12-15T14:56:38.219272 #28376]  INFO -- :   0.000000   0.000000   0.000000 (  0.000362)

Sure it is not the Ruby’s interpreter problem since vanilla 10k loop only took 0.3 msec. Comparable to native speed, huh? But we found several interesting findings:

  • using #each is faster than using #find_in_batches (480 ms to 260 ms)
  • … What “{Model} Load (some ms)” means? It does not even reflect the real load time. “Metadata Load 40.9 ms”, meanwhile the real load time is 268 ms. If we compare from logger timestamp, it is also around 268 ms (38.218 – 37.949). Seems like internally they use different measurement tip.
  • For each group in find_in_batches, the “Metadata Load” itself does not reflect the real time to yield the method. If we compare each DEBUG line, it will be around 40ms (e.g.: Look 37.550252 – 37.516353), pretty far from 3.2ms.

So what is my solution for processing 10k records? I don’t know. The page itself will rarely change, so caching the request will help it much. But i am still curious, why processing only 10k records can took up to 800ms.

Also, i am still curious why “Model Load” says faster than the time it is actually need. It took 40ms between yield but it says loaded in 3.2ms.

Lembar Kerja dalam Tiga Puluh Baris Javascript

| Comments

Spreadsheet

Saya menjelajahi kembali beberapa tautan yang ada di penanda saya. Saya menemukan tautan yang sangat menarik beberapa tahun lampau: membuat lembar kerja dalam 30 baris di Javascript. Woh!

Baiklah, untuk membuat cerita lebih pendek, saya melihat kembali dimana keajaiban baris kode tersebut. Anda dapat melihat kode sumber aslinya di Codepen (via HN). Pertanyaan saya ada dua:

  • Bagaimana menyelesaikan formula di sana?
  • Bagaimana dependency resolution antar sel di lembar kerja tersebut?

Mari kita bahas yang pertama. Ini menjadi hal yang menarik bagi saya karena penyelesaian formula berarti Anda membuat DSL sendiri, menerjemahkannya, dan mengeksekusinya. DSL yang digunakan oleh semua program lembar kerja serupa. DSL yang digunakan juga dekat dengan bahasa pemrograman pada umumnya. Ya, menguraikan formula menjadi hal yang dimengerti dan dieksekusi oleh mesin bukanlah hal yang sederhana.

Ternyata, program Javascript ini tidak memiliki DSL sendiri. Alih-alih, program ini menggunakan perintah eval sederhana untuk menguraikan dan mengeksekusi formulanya. Yup, tentu saja jika Anda memasukkan perintah Javascript yang aneh-aneh —– bukan bentuk formula lembar kerja pada umumnya —– Anda akan menemukan sedikit kejanggalan. Anda dapat memberikan perintah alert pada perintahnya dan alert akan bena-benar muncul. Tentu saja bukan suatu hal yang akan Anda lakukan pada mesin produksi, tetapi ini sudah sangat baik sebagai sebuah PoC.

Mari kita bahas yang kedua: bagaimana dependency resolution dilakukan? Anggap saya memiliki sel A1 berisi =B2 * 5 dan B2 yang berisi =100*C2 dan C2 yang berisi 99? Bagaimana penyelesaian keterkaitan formula yang berbeda sel? Ah, “keajaiban” bahasa yang interpret di sini.

Anda dapat melihat di kode tersebut: ada perintah yang membuat property. Lihat pada bagian ini:

var getter = function() {
    var value = localStorage[elm.id] || "";
    if (value.charAt(0) == "=") {
        with (DATA) return eval(value.substring(1));
    } else { return isNaN(parseFloat(value)) ? value : parseFloat(value); }
};
Object.defineProperty(DATA, elm.id, {get:getter});
Object.defineProperty(DATA, elm.id.toLowerCase(), {get:getter});

Ini adalah dokumentasi dari MDN:

Object.defineProperty(obj, prop, descriptor)

Parameters

  • obj
    The object on which to define the property.
  • prop
    The name of the property to be defined or modified.
  • descriptor
    The descriptor for the property being defined or modified.

Jadi, pada dasarnya, eval akan dilakukan dengan property di dalam object bernama DATA. Property-nya sendiri bersifat dinamis, di dalam fungsi. Properti baru benar-benar didapatkan nilainya apa ketika dieksekusi. Jadi, sebenarnya yang melakukan dependency resolution adalah eval itu sendiri. Program ini tidak benar-benar membuat dependency graph seperti yang saya bayangkan sebelumnya.

Sebagai contoh, ada formula =A9*Math.cos(B2). Yang terjadi adalah Javascript eval akan mengevaluasi A9 * Math.cos(B2) ke dalam sel tersebut. Math.cos adalah fungsi bawaan Javascript sehingga tidak perlu dipertanyakan. Bagaimana dengan A9 dan B2? Dua variabel tersebut adalah property dalam objek DATA yang dapat diakses tanpa mengetikkan DATA berkat idiom with. Apa nilai A9 dan B2? Belum tahu. Akan tetapi, karena A9 dan B2 itu sendiri merupakan property yang memiliki getter sendiri, maka kemudian eval akan mengeksekusi DATA.A9 dan DATA.B2 — yang mana di dalam property tersebut akan melakukan eval lagi jika diawali dengan =.

Bagaimana jika ada circular reference? Ini diakali dengan try-catch sederhana berikut:

INPUTS.forEach(function(elm) { try { elm.value = DATA[elm.id]; } catch(e) {} });

Jika terdapat circular reference, call-stack akan menjadi sangat panjang. Bayangkan B2 menggunakan nilai dari A1 dan A1 menggunakan nilai dari B2. Akan terjadi sebuah pemanggilan fungsi getter terhadap dua sel berbeda yang tidak ada habisnya. Ketika call stack sangat panjang, interpreter akan menghasilkan exception, yang ditangkap, dan diabaikan.

Saya rubah kode asli agar menangkap exception dan mencetaknya. Beriktu hasilnya:

Recursion

Resolusi Monitor dan DPI

| Comments

Saya sudah berencana sejak lama sekali untuk mengganti monitor saya dari 17” dengan resolusi 1440x900 yang menurut saya cukup kekecilan untuk mata saya. Jika resolusi 17” tersebut kekecilan, berapa yang “tidak kekecilan” bagi saya? Untuk itu, saya perlu mendefinisikan apa itu “kekecilan.”

“Terlalu kecil” terkait dengan ukuran teks-teks yang ada di layar. Itu definisi “terlalu kecil” yang cukup jelas. Baiklah, question time! Pastinya akan muncul pertanyaan, “kalau teksnya kekecilan, ya yang digedein teksnya! Bukan monitornya!” Tepat sekali. Itulah yang dilakukan para pengguna Mac Retina Display. Di MBPr, ada setting bernama “scaled resolution” yang melakukan emulasi resolusi layar ke resolusi “yang diinginkan”. Sekali lagi, ini emulasi. Yang di-render sebenarnya tetap resolusi native. Oleh karena itulah, kalau mengembangkan aplikasi untuk Mac, kita perlu menyertakan raster untuk DPI yang berbeda — agar hasil rendernya optimal.

Sayang sekali, emulasi resolusi (“scaled resolution”) hanya ada di Mac. Android memang memiliki fitur membesarkan ukuran teks, tetapi hanya teks. Untuk memperburuk suasana, tidak semua aplikasi respect dengan setelan ukuran teks ini. Alasannya karena layout nya nanti rusak, katanya.

Windows juga memiliki fitur “membesarkan ukuran”. Sayang sekali, pengaturan ini tidak berjalan dengan semestinya. Beberapa program malah rusak dan tidak respect dengan setelan ini.

Oleh karena itu, daripada bergantung dengan software dan saya belum bisa mengafford Mac, jadi lebih baik saya mencari monitor yang menghasilkan teks yang lebih besar. Dan tentu saja, space yang lebih lega — akan tetapi ukuran teks yang lebih besar menjadi penting.

Ukuran teks bisa dinilai dari ppi. Makin tinggi angka ppi, maka makin kecil piksel yang dihasilkan, maka makin kecil ukuran teksnya. Oleh karena itu, saya perlu mengukur berapa “ppi” yang pas untuk saya. Setelah melihat banyak sekali monitor, saya layar 19” dengan resolusi 1440x900 sangat cocok untuk mata saya. Ukurannya enggak bikin sakit untuk dilihat dalam waktu lama. Akan tetapi, ruang 1440x900 terlalu kecil untuk saya, maka saya perlu mencari ruang yang lebih besar.

Jadi, saya akan mencari monitor dengan ppi yang lebih rendah / mendekati ppi monitor 1440x900 di 19” dengan resolusi yang lebih tinggi. Awalnya, saya sangat kesemsem dengan layar 2K di ukuran 27”. Akan tetapi, setelah mengetahui harga monitor 4K tidak jauh berbeda, saya jadi kesemsem juga.

Sayang sekali, ketika melihat ppi nya, saya kecewa. Berikut rangkuman kalkulasi PPI nya:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

1440x900
  - 17: 99 <--- Terlalu kecil
  - 19: 89 <--- Ideal

1920x1080
  - 22: 100
  - 23: 95 <-- Terlihat acceptable
  - 24: 91 <-- Sepertinya cukup
  - 25: 88 <-- Ideal
  - 27: 81 <-- Terlalu besar? Kecuali meja saya juga panjang

2560x1440
  - 25: 117
  - 27: 108
  - 32: 92

4K
  - 34: 86

Untuk kondisi sekarang, ukurang yang ideal adalah 24-25 dengan resolusi FHD. Lebih tinggi dari itu, memang space nya cukup lapang (resolusinya lebih tinggi), namun sayangkan ppi terlalu kecil untuk saya.

Ya begitulah.

Tentu saja, ppi yang nyaman untuk setiap orang berbeda-beda :D

Cara Googling

| Comments

Beberapa orang mengeluhkan kepada saya karena gagal menemukan masalah pemrograman apa yang ingin dicari di Google. Berikut adalah panduan singkat berdasarkan keluhan-keluhan yang coba saya pahami.

In Ruby, Everything is Evaluated

| Comments

So if i write

1
2
3
def hello
  puts 'world'
end

It will evaluate def, to which Ruby will “create a method named hello in global scope, with puts ‘world’ as a block”. We can change “global scope” to any object we want.

1
2
3
4
5
class Greeting
  def hello
    puts 'world'
  end
end

The class “Greeting” is actually EVALUATED, NOT DEFINED (e.g. In Java, after we define a signature of a class/method, we can’t change it, except using reflection). So actually, we can put anything in “Greeting” block, like

1
2
3
4
5
6
class Greeting
  puts "Will define hello in greeting"
  def hello
    puts 'world'
  end
end

Save above script as “test.rb” (or anything) and try to run it. It will show “Will define hello in greeting” EVEN you don’t call “Greeting” class or “hello” class or you don’t even need to instantiate “Greeting” class. This language feature allows meta programming, like what we see in Rails.

This time i will use Class Attribute within active support. If you ever run Rails, you should have it, but you can gem install active_support if you don’t.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
require 'active_support/core_ext/class/attribute'

module Greeting; end

class Greeting::Base

  class_attribute :blocks

  def hello(name)
    self.blocks[:greeting].call(name)
    self.blocks[:hello].call(name)
  end

  protected
  def self.define_greeting(sym, &blk)
    self.blocks ||= {}
    self.blocks[sym] = blk
  end
end

class Greeting::English < Greeting::Base
  define_greeting :greeting do |who|
    puts "Hi #{who}, Ruby will greet you with hello world!"
  end
  define_greeting :hello do |who|
    puts "Hello World, #{who}!"
  end
end

class Greeting::Indonesian < Greeting::Base
  define_greeting :greeting do |who|
    puts "Halo kakak #{who}, Ruby akan menyapamu dengan Halo Dunia!"
  end
  define_greeting :hello do |who|
    puts "Halo dunia! Salam, #{who}!"
  end
end

x = Greeting::English.new
x.hello "Fido"
# Hi Fido, Ruby will greet you with hello world!
# Hello World, Fido!
x = Greeting::Indonesian.new
x.hello "Fido"
# Halo kakak Fido, Ruby akan menyapamu dengan Halo Dunia!
# Halo dunia! Salam, Fido!

Previously i want to move the class attribute logic to above code, but after i see the Active Support code, it is pretty complex, so i just require it : /


Previously, i posted this in Reddit