Pengguna bahasa pemrograman pyhton hendaknya mengetahui tentang Zen dari python: sebuah prosa dan pemikiran yang dibuat oleh Tim Peters, salah seorang ahli python. Zen ini berisi kumpulan praktek terbaik (best practice) serta cara-cara penulisan kode python yang disarankan (idiomatic, pythonic).

Bagaimana kita dapat membaca Zen dari python? Mudah saja, cukup masukkan perintah import this pada intepreter:

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Untuk mempermudah lagi, berikut adalah terjemahan bebas dari prosa tersebut oleh penulis:

Zen tentang Python, oleh Tim Peters

Yang elok lebih baik daripada yang buruk.
Yang eksplisit lebih baik daripada yang implisit.
Yang sederhana lebih baik daripada yang kompleks.
Yang kompleks lebih baik daripada yang rumit.
Yang merata lebih baik daripada yang bersarang.
Yang renggang lebih baik daripada yang padat.
Kemudahan Pembacaan penting.
Kasus khusus tidak cukup istimewa untuk melanggar aturan.
Walaupun kepraktisan lebih penting daripada kemurnian.
Kesalahan tidak boleh dilewatkan diam-diam.
Kecuali didiamkan dengan gamblang.
Jika dihadapkan dengan hal yang ambigu, tolak lah godaan untuk menebak.
Hanya boleh ada satu -- dan kalau bisa satu saja -- cara yang jelas untuk melakukan sesuatu.
Walaupun bisa saja cara tersebut tidak jelas pada awalnya, kecuali anda orang Belanda.
Sekarang lebih baik daripada tidak pernah sama sekali.
Meskipun tidak pernah sama sekali seringkali lebih baik daripada *sekarang*.
Jika implementasinya sulit dijelaskan, maka gagasan tersebut buruk.
Jika implementasinya mudah dijelaskan, maka gagasan tersebut mungkin saja baik.
"Namespace" adalah ide yang sangat baik sekali -- mari gunakan fitur ini lebih dan lebih lagi!

Oke, setelah diterjemahkan (dan kemungkinan semakin bingung), mari kita lihat penjelasan dari prosa ini satu per satu. Mudah-mudahan dengan melihat penjelasan dari prosa ini, kita dapat lebih mengerti dan dapat menuliskan kode python yang baik!

Beautiful is Better Than Ugly

Dalam menuliskan kode program, hendaknya kita tidak hanya memperhatikan sisi fungsionalitas dari kode tersebut, tetapi juga keindahan dari kode. Misalkan, untuk menghitung FPB (Faktor Pengali terBesar) dengan menggunakan metode Euler, kita dapat menuliskan kodenya tanpa memperhatikan keindahan kode seperti berikut:

def gcd(x, y):
    while x != y:
        if x > y:
            x = x - y
        else:
            y = y - x
    return x

atau kita dapat menuliskan kode dengan fungsionalitas yang sama dengan lebih indah:

def gcd(x, y):
    while y:
        x, y = y, x % y
    return x

Lebih sederhana, lebih indah, dan pastinya, lebih gampang dirawat. Tentu saja “keindahan” adalah sebuah hal yang subjektif, tetapi hendaknya kita berusaha untuk menghasilkan kode yang paling indah, elegan, dan sederhana untuk mendapatkan kemudahan perawatan (maintainability) kode.

Explicit is Better than Implicit

Hendaknya kita selalu menuliskan kode secara jelas: jika ada fungsi yang diambil dari modul tertentu, maka kita sebaiknya menuliskan dari mana fungsi tersebut berasal. Hal ini menyebabkan kode berikut:

from os import *
print getcwd()

buruk, karena kita tidak dapat mengetahui apakah getcwd berasal dari paket os atau adalah fungsi pada bagian lain dari modul. Memanggil import * berarti membuat os menjadi implisit. Kode yang lebih baik adalah seperti berikut:

import os
print os.getcwd()

sehingga kita dapat langsung melihat bahwa getcwd merupakan fungsi yang diambil dari paket os. Mengetahui dari paket mana sebuah fungsi berasal akan sangat membantu kita dalam membaca kode, dan kemudahan pembacaan kode akhirnya akan menghasilkan kemudahan perawatan kode.

Simple is Better than Complex

Larik ini berhubungan dengan filsafat python yang selalu mengedepankan kesederhanaan implementasi (lihat juga larik x dan y). Idealnya, dalam mengimplementasikan sebuah kebutuhan, kita harus mencari implementasi yang paling sederhana untuk mengurangi kesalahan implementasi ataupun interpretasi.

Misalnya, jika diminta untuk menyimpan dan membaca data dari sebuah sistem, kebanyakan orang akan secara alami berpikir untuk menyimpan dan membaca data tersebut dari basis data. Hal ini tidak selalu benar. Kode untuk bekerja dengan basis data tentunya akan jauh lebih kompleks dibandingkan dengan kode untuk bekerja dengan file misalnya. Kode di bawah memperlihatkan contoh kode yang membaca dan menyimpan data ke dalam file, di mana data diambil dari basis data:

def store(measurements):
    import sqlalchemy
    import sqlalchemy.types as sqltypes

    db = sqlalchemy.create_engine('sqlite:///measurements.db')
    db.echo = False
    metadata = sqlalchemy.MetaData(db)
    table = sqlalchemy.Table('measurements', metadata,
        sqlalchemy.Column('id', sqltypes.Integer, primary_key=True),
        sqlalchemy.Column('weight', sqltypes.Float),
        sqlalchemy.Column('temperature', sqltypes.Float),
        sqlalchemy.Column('color', sqltypes.String(32)),
        )
    table.create(checkfirst=True)

    for measurement in measurements:
        i = table.insert()
        i.execute(**measurement)

Bandingkan kode di atas dengan kode berikut, yang melakukan hal yang sama, minus basis data (data disimpan dalam file json):

def store(measurements):
    import json
    with open('measurements.json', 'w') as f:
        f.write(json.dumps(measurements))

Mana yang lebih sederhana, dan mudah dimengerti? Ingat juga bahwa semakin sedikit kode yang harus kita tuliskan, semakin kecil juga kemungkinan sebuah bug dapat terjadi. Berusahalah untuk menghasilkan kode dan implementasi yang sederhana, sehingga kita dapat menghindari bug ataupun fitur-fitur yang tidak diperlukan dan / atau membingungkan pengguna.

Complex is Better than Complicated

Menyambung dengan masalah pada bagian sebelumnya, bagaimana jika memang permasalahannya kompleks? Akan sangat sulit sekali, misalnya jika kita harus menyederhanakan penyusunan DNA manusia bukan? Atau mungkin data telah terlanjur disimpan dalam basis data pada contoh sebelumnya. Apa yang harus kita lakukan?

Utamanya ialah jangan memperumit masalah dengan menambahkan komponen-komponen tambahan yang menyebabkan kode semakin sulit dimengerti. Masih dengan contoh sebelumnya, alih-alih menggunakan sqlalchemy, sebuah komponen kompleks dengan sangat banyak fungsionalitas, kita dapat menyelesaikan masalah dengan menggunakan query MySQL sederhana (jika basis data yang digunakan adalah MySQL), sehingga kita dapat langsung menyelesaikan masalah dengan hanya pengertian akan MySQL. Penggunaan sqlalchemy akan mengharuskan kita mengerti python, MySQL, dan sqlalchemy.

def store(measurements):
    import MySQLdb
    db = MySQLdb.connect(user='user', passwd="password", host='localhost', db="db")

    c = db.cursor()
    c.execute("""
        CREATE TABLE IF NOT EXISTS measurements
          id int(11) NOT NULL auto_increment,
          weight float,
          temperature float,
          color varchar(32)
          PRIMARY KEY id
          ENGINE=InnoDB CHARSET=utf8
          """)

    insert_sql = (
        "INSERT INTO measurements (weight, temperature, color) "
        "VALUES (%s, %s, %s)")

    for measurement in measurements:
        c.execute(insert_sql,
            (measurement['weight'], measurement['temperature'], measurement['color'])
            )

Tidak masalah jika kita tidak dapat menghindari kerumitan masalah karena memang masalahnya rumit. Yang tidak boleh dilakukan ialah menambahkan kerumitan di atas kerumitan yang sudah ada.

Complex vs Complicated
Untuk pembaca yang tidak mengerti perbedaan complex dan complicated, berikut adalah definisi dari kamus mengenai kedua kata ini (penekanan diberikan oleh penulis)

Complex: involving a lot of different but related parts.
Complicated: involving a lot of different parts, in a way that is difficult to understand.

Secara sederhana, perbedaannya ada pada kemudahan sistem dimengerti. Sebuah sistem yang complex bisa saja terdiri dari banyak bagian, tetapi sistem yang complicated adalah sistem complex yang sulit dimengerti.

Flat is better than nested

Aturan yang cukup mudah dimengerti, dalam konteks pemrograman. Mana yang lebih mudah dibaca, dirawat, dan dimengerti, kode berikut:

if kondisi1:
    if kondisi2:
        if kondisi3:
            # kode 1
    else:
         # kode 2
else:
     # kode 3

atau kode di bawah ini:

if not kondisi1:
    # Kode 3
elif not kondisi2:
    # Kode 2
elif kondisi1 and kondisi2 and kondisi3:
    # kode 1

Semakin banyak indentasi yang harus kita ketikkan, maka semakin sulit juga kita mengetahui bagian kode mana yang adalah bagian dari else atau if yang mana. Penulis cukup yakin bahwa tidak akan ada orang yang ingin menjadi penanggung jawab dari kode ekstrim dengan 14 tingkat indentasi.

Sparse is better than dense

Larik ini berbicara tentang cara kita membagikan kode ke dalam beberapa bagian, dengan menggunakan media baris kosong atau spasi. Hal sepele seperti baris kosong seringkali dilupakan oleh penulis kode, dan baru diingat 3 bulan setelah penulisan kode, di tengah-tengah rasa frustasi mencari bug.

Perhatikan kode ini:

def process(response):
    selector = lxml.cssselect.CSSSelector('#main > div.text')
    lx = lxml.html.fromstring(response.body)
    title = lx.find('./head/title').text
    links = [a.attrib['href'] for a in lx.find('./a') if 'href' in a.attrib]
    for link in links:
        yield Request(url=link)
    divs = selector(lx)
    if divs: yield Item(utils.lx_to_text(divs[0]))

dan bandingkan dengan ketika kita menambahkan beberapa baris kosong:

def process(response):
    lx = lxml.html.fromstring(response.body)

    title = lx.find('./head/title').text

    links = [a.attrib['href'] for a in lx.find('./a') if 'href' in a.attrib]
    for link in links:
        yield Request(url=link)

    selector = lxml.cssselect.CSSSelector('#main > div.text')
    divs = selector(lx)
    if divs:
        bodytext = utils.lx_to_text(divs[0])
        yield Item(bodytext)

Bukankah kode yang sama, ditambah dengan beberapa baris kosong menjadi sangat enak dipandang? Kode tersebut juga menjadi lebih mudah dimengerti karena terdapat pemisahan antar bagian dari kode yang masing-masing melakukan hal berbeda (parsing, ambil teks pada judul, cari link, dst).

Readability Counts

Seluruh poin-poin yang kita bahas sampai titik ini berfokus kepada satu hal: kemudahan pembacaan kode. Kemudahan pembacaan kode merupakan hal yang sangat penting dalam pemrograman, karena pembacaan dan perawatan kode adalah hal yang paling banyak kita lakukan.

Sebuah eksperimen berpikir, mana yang lebih enak dibaca dan dirawat, dengan asumsi pembaca menguasai python dan berbagai konsepnya dengan baik:

halve_evens_only = lambda nums: map(lambda i: i/2, filter(lambda i: not i%2, nums))

atau:

def halve_evens_only(nums):
    return [i/2 for i in nums if not i % 2]

Kode di atas juga berlaku untuk larik x.

Special Case

Larik berikutnya memiliki dua kalimat, yaitu

Special cases aren’t special enough to break the rules.
Although practicality beats purity.

Baris ini berarti segala hal yang telah ditentukan oleh bahasa pemrograman tidak perlu memiliki pengecualian.

Misalnya, karena dukungan akan OOP (Object Oriented Programming), maka segala hal yang ada di dalam python merupakan objek. Hal ini kontras dengan Java yang memberikan pengecualian terhadap tipe data primitif, yang tidak dianggap objek.

Tetapi terkadang membuat semua hal menjadi objek tidak praktis. Jika kita membuat sebuah kelas, yang bernama “Two”, yang mewakili nilai dua:

class Two(int):
    pass

dan ingin melakukan pencetakan, maka hasil yang kita dapatkan adalah hasil int yang diberikan ke Two:

print(Two(1))
1

print(Two(4))
4

Seringkali pada kasus seperti ini yang kita inginkan adalah menghasilkan nilai “2” setiap kali kita mencetak objek Two. Python mengorbankan “kemurnian” OOP dalam hal ini dengan memberikan variabel khusus __str__ untuk mencetak objek, seperti berikut:

Two.__str__ = lambda x: '2'

Sehingga setiap kali pemanggilan print, kita akan mendapatkan hasil yang diinginkan:

print(Two(1))
2

print(Two(4))
2

Pada akhirnya kita harus memperlakukan segala aspek yang ada dalam kode dengan sama, sambil tetap memperhatikan dunia praktisi. Kemurnian implementasi secara akademik tidak akan berguna jika tidak dapat digunakan untuk menyelesaikan permasalahan dunia nyata.

Penanganan Error

Sama seperti larik sebelumnya, larik ini terdiri dari dua bagian:

Errors should never pass silently.
Unless explicitly silenced.

Lariknya sendiri cukup jelas, yaitu bahwa setiap error harus ditangani dengan jelas dan eksplisit. Jadi, kode berikut:

try:
    import json
except ImportError:
    print("Modul tidak ditemukan")

tidak baik karena kesalahan program (bahwa modul json tidak ditemukan) ditangkap, tetapi diberikan pesan kesalahan yang tidak tepat. Jika kode digunakan, ketika menemukan kesalahan impor maka kita akan sulit mendeteksi apa yang salah, terutama jika kita hanya salah mengetikkan json menjadi josn misalnya.

Hal yang harus kita lakukan jika memang ingin menangani error dengan benar ialah langsung menjalankan kode agar error tersebut tidak mempengaruhi kode-kode di bawahnya. Misalkan:

try:
    v = d[k]
except KeyError:
    v = d[k] = default

Atau, dengan contoh impor modul yang sama:

try:
    import json
except ImportError:
    try:
        import simplejson as json
    except:
        print("Modul untuk operasi JSON tidak dapat ditemukan!")
        raise SystemExit(-1)

In the Face of Ambiguity, Refuse the Temptation to Guess

Jika dihadapkan dengan keadaan atau data yang ambigu, selayaknya kita tidak mencoba menebak atau membuat keputusan secara langsung. Misalnya jika kita ingin menyimpan sebuah data HTTP ke dalam basis data, maka terlebih dahulu kita harus mengecek encoding dari data yang dikirimkan. Tanpa pengecekan encoding dengan benar dapat menyebabkan lobang keamanan pada sistem yang dibangun.

Jadi, alih-alih menuliskan kode seperti berikut:

def process(response):
    db.store(url, response.body)

akan lebih baik jika kita melakukan pengecekan encoding terlebih dahulu:

def process(response):
    charset = detect_charset(response)
    db.store(url, response.body.decode(charset))

sehingga kita dapat memproses dan menghindari berbagai lobang keamanan yang disebabkan oleh kesalahan penyimpanan data teks.

There Can Only be One

Dua larik selanjutnya:

There should be one, and preferably only one way to do it.
Although that way may not be obvious at first unless you’re Dutch.

menceritakan bagaimana idealnya dalam menuliskan program atau library, idealnya kita hanya memiliki satu cara untuk melakukan suatu hal. Lebih dari satu cara akan membingungkan penggguna, yang tentunya adalah hal yang tidak kita inginkan. Baris kedua sendiri adalah sebuah candaan, karena pencipta python adalah orang Belanda.

Contoh dari filosofi ini dapat dilihat langsung di dalam bahasa python sendiri. Jika pada bahasa yang lain kita memerlukan syntax khusus untuk penulisan kondisi inline (contohnya pada bahasa C):

a = (check())? 10: 11

// sama dengan
if (check()) {
    a = 10
} else {
    a = 11
} 

Maka dalam python kita menggunakan semantik yang sama, yaitu if:

a = 10 if check() else 11

Contoh lainnya adalah untuk iterasi dari sekumpulan data. Dalam python, hanya terdapat satu cara utama untuk iterasi data, yaitu for:

sequences = [
    range(20),
    {'foo': 1, 'fie': 2},
    fibonacci_generator(),
    (5, 3, 3)
]

for sequence in sequences:
    for item in sequence:
        pass

Tidak peduli apakah data tersebut adalah map, dictionary, list, ataupun tuple, selama data merupakan data koleksi (kumpulan), maka kita dapat melakukan iterasi dengan menggunakan for.

Kapan Kita Harus melakukan X?

Jawaban dari pertanyaan di atas ada pada larik berikutnya:

Now is better than never
Although never is often better than right now.

Misalnya, jika anda adalah seorang penulis library yang ingin mengganti sebuah fungsi lama dengan fungsi baru (katakanlah karena jumlah parameter fungsi lama terlalu banyak, dan ingin anda sederhanakan), maka daripada menuliskan peringatan deprecated pada dokumentasi fungsi lama:

# Deprecated. Please do not use as of version 10.1. This function will
# be deleted on version 10.2!
def old_function(a, b, c, d, e, f, g):

kita dapat langsung menggantikan implementasi fungsi tersebut dengan penanda kesalahan:

def old_function(a, b, c, d, e, f, g):
    raise DeprecationWarning

sehingga semua pengguna library (mudah-mudahan) pasti akan membaca pesan kesalahan dan berpindah ke fungsi baru. Tetapi tentunya kita harus berusaha untuk meminimalkan perubahan seperti ini, karenanya “although never is often better than right now”.

Larik baris kedua ini juga harus selalu diingat ketika mengimplementasikan fungsi baru. Seringkali kita lupa bahwa terkadang pengguna tidak memerlukan atau menginginkan perubahan atau penambahan fungsi. Jika perubahan tidak diperlukan, jangan lakukan perubahan. If it ain’t broken, don’t fix it!

Sulit vs Gampang

Terkadang terdapat satu stigma dalam dunia pemrograman bahwa semakin rumit sebuah implementasi, semakin “keren” atau “jenius” orang yang mengimplementasikan atau yang mengerti implementasi tersebut. Penulis (dan kelihatannya, Zen dari Python) tidak setuju dengan hal tersebut. Terkadang hal yang paling sulit justru adalah menyederhanakan hal yang sangat kompleks. Kedua larik selanjutnya juga berkata sama:

If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.

Misalnya, kita akan membandingkan dua library yang dapat digunakan untuk mengolah data XML. Paket pertama, xml.dom.minidom mewajibkan kita untuk mengerti tentang DOM (Document Object Model) untuk mencari data dalam XML:

import xml.dom.minidom
document = xml.dom.minidom.parseString(
    '''<menagerie><cat>Fluffers</cat><cat>Cisco</cat></menagerie>''')
menagerie = document.childNodes[0]
for node in menagerie.childNodes:
    if node.childNodes[0].nodeValue== 'Cisco' and node.tagName == 'cat':
        return node

Sementara paket kedua, lxml, memanfaatkan hal-hal mendasar pada python untuk melakukan hal yang sama. Misalnya, karena data adalah sekumpulan informasi, maka kita dapat melakukan iterasi dan operasi koleksi umumnya pada data tersebut:

import lxml
menagerie = lxml.etree.fromstring(
    '''<menagerie><cat>Fluffers</cat><cat>Cisco</cat></menagerie>''')
for pet in menagerie.find('./cat'):
    if pet.text == 'Cisco':
        return pet

sehingga pengunaan lxml menjadi mudah dan alami jika kita mengerti python dengan baik. Satu acuan bagi penulis yaitu jika anda tidak dapat menjelaskan sebuah hal sampai yang mendengarkan penjelasan anda berkata “cuma itu?” maka penjelasan tersebut tidak cukup sederhana. Penjelasan yang tidak cukup sederhana dapat diperbaiki dengan menyederhanakan penjelasan, atau memoles idenya lagi.

Namespaces are one honking great idea – let’s do more of those!

Cukup jelas yang dikatakan larik ini. Namespace, sebagai alat untuk memodularkan kode, adalah ide yang sangat baik. Sering-seringlah menggunakan namespace!

Mana yang lebih mudah:

def chase():
     import menagerie.models.cat as cat
     import menagerie.models.dog as dog

     dog.chase(cat)
     cat.chase(mouse)

atau:

def chase():
     import menagerieModelsCat
     import menagerieModelsDog

     menagerieModelsDog.chase(cat)
     menagerieModelsCat.chase(mouse)

?

Akhir Kata

Sekian saja penjelasan mengenai Zen dari Python, semoga bermanfaat untuk pembaca. Jika ada pertanyaan, masukan, ataupun kritik, silahkan tulis di bagian komentar :)

comments powered by Disqus

Daftar Isi