Perbaiki kode python-mu dan berhenti jadi jamet
Warning: maybe heavily opinionated
Menurut saya python adalah bahasa yang relatif elegan. Kita bisa menyelesaikan banyak hal dengan sedikit baris kode saja. Sintaksnya jelas dan cenderung mirip bahasa inggris. Dia sangat ramah digunakan untuk proyek individu maupun dalam tim.
Sayang sekali banyak saya temui kode jamet jele, walaupun sudah ada panduan penulisan standar untuk kode python. Beberapa di antaranya pernah saya temukan juga di suatu proyek (yang ngakunya) profesional. No mention 🙅🏻♂️.
Saya coba tunjukkan kejametan kode python seperti contoh yang saya buat (dengan susah payah keringat darah) berikut ini:
def Concat(S1, S2):
return S1+S2
class Manipulate:
def remove_number(self, s):
result = ""
for i in range(len(s)):
if not s[i].isnumeric():
result += s[i]
return result
def removeWhiteSpace(self, s):
result = ""
for i in range(len(s)):
if not s[i].isspace():
result += s[i]
return result
someText = Concat("ABC 123", "DEF 456")
manipulate = Manipulate()
print("Original input : ", someText)
print("Input without numbers : ", manipulate.remove_number(someText))
print("Input without spaces : ", manipulate.removeWhiteSpace(someText))
Intinya, ada satu fungsi untuk menggabung 2 string, dan satu kelas berisi method untuk memanipulasi string. Output memang seperti yang diharapkan:
$ python jamet.py
Original input : ABC 123DEF 456
Input without numbers : ABC DEF
Input without spaces : ABC123DEF456
Sekarang mari kita coba preteli jelek-jeleknya.
Blank lines
Kode di atas membuat mata jadi sepet karena kurang memanfaatkan garis blank lines (garis kosong). PEP-8 menyarankan penggunaan blank lines, minimal sebagai berikut:
- Gunakan dua baris blank lines untuk memisahkan:
- Dua top-level function
- Dua kelas
- Top-level function dengan kelas
- Gunakan satu baris blank lines untuk memisahkan method dalam kelas.
Penamaan
Pertama, kelas Manipulate
adalah contoh buruk dari penamaan kelas.
Kelas seharusnya menggunakan frasa kata benda (noun phrase), karena kelas merepresentasikan suatu entitas.
Dalam hal ini, Manipulate
seharusnya diubah menjadi, misalnya, Manipulator
karena tujuannya adalah melakukan manipulasi string.
Bahkan, lebih baik lagi menjadi StringManipulator
.
Aturan ini berlaku tidak hanya untuk python saja.
Untuk urusan gaya penamaan, poin-poin berikut ini bisa menjadi acuan:
- Nama kelas menggunakan
CapsWord
. - Nama fungsi, method, dan variabel (termasuk parameter fungsi) menggunakan
snake_case
.- Fungsi
Concat
seharusnyaconcat
,S1
danS2
harusnyas1
dans2
. - Method
removeWhiteSpace
seharusnyaremove_white_space
. - Variabel
someText
seharusnyasome_text
.
- Fungsi
Trivia: sebetulnya kita bisa saja mengganti parameter
self
tiap-tiap method dengan nama lain. Namun, marilah menjaga perdamaian dunia dengan tetap menggunakan namaself
seperti coder python waras lainnya.
Bro... Move on dari C-style looping
Perhatikan potongan kode ini:
...
def remove_number(self, s):
result = ""
for i in range(len(s)):
if not s[i].isnumeric():
result += s[i]
return result
...
Tidak ada error.
Kode melakukan apa yang diharapkan: iterasi tiap karakter di s
dengan bantuan indeks i
dalam range 0 hingga len(s)
(eksklusif).
Tambahkan s[i]
ke variabel result
kalau si karakter bukan numerik.
Di python, kita diberi kemudahan dengan diizinkan iterasi string, list, atau iterable lainnya, lansung (ngomong-ngomong, saya ganti nama parameter s
menjadi text
biar lebih jelas):
...
def remove_number(self, text):
result = ""
for c in text:
if not c.isnumeric():
result += c
return result
...
Bagaimana kalau kita butuh mengakses indeks-nya? Gampang. Pakai enumerate:
for i, c in enumerate(text):
print(f"Character {c} at index {i}")
Sebagai bonus, python memberikan kita fungsionalitas lebih, sehingga kita bisa menulis kode yang lebih ✨pythonic✨:
...
def remove_number(self, text):
result = "".join(c for c in text if not c.isnumeric())
return result
...
Sampai di sini, setelah kode di atas dipermak, kita akan mendapatkan kode yang lebih bersih seperti dibawah ini.
Pro-tip: letakkanlah driver code di dalam blok statement if __name__ == "__main__":
agar output hanya keluar saat file ini langsung dijalankan di terminal.
Jika tidak, output akan keluar juga saat file ini di-import di file python lainnya.
def concat(s1, s2):
return s1 + s2
class StringManipulator:
def remove_number(self, text):
result = "".join(c for c in text if not c.isnumeric())
return result
def remove_white_space(self, text):
result = "".join(c for c in text if not c.isspace())
return result
if __name__ == "__main__":
some_text = concat("ABC 123", "DEF 456")
manipulator = StringManipulator()
print("Original input : ", some_text)
print("Input without numbers : ", manipulator.remove_number(some_text))
print("Input without spaces : ", manipulator.remove_white_space(some_text))
Step-up the game with type annotation
Python, seperti yang kita tahu, adalah bahasa yang menganut dynamic typing. Artinya, kita tidak perlu menyertakan tipe data untuk tiap variabel yang kita buat. Namun, python mendukung penggunaan type annotation agar kita bisa menyertakan tipe data. Saya sangat menyarankan type annotation ini, walaupun pada akhirnya interpreter mengabaikan.
Type annotation membuat kode semakin eksplisit. Ini akan sangat membantu jika kita bekerja dalam tim. Rekan kita tidak perlu banyak waktu untuk mengerti tujuan suatu fungsi karena bisa mengira-ngira dari tipe data nama fungsi, tipe data parameter, dan tipe data return value dari fungsi.
Perhatikan fungsi di bawah ini:
from typing import List
def print_self_introduction(
first_name: str, last_name: str, age: int, hobbies: List[str]
) -> None:
print(f"Hi, there! My name is {first_name} {last_name}.")
print(f"I am {age} years old.")
# Make a copy of the list to prevent altering original list.
# Later, the last item of this list will be prepended with
# "and" to print hobbies more naturally.
formatted_hobbies = hobbies.copy()
if len(formatted_hobbies) > 2:
formatted_hobbies[-1] = f"and {formatted_hobbies[-1]}"
joint_hobbies = ", ".join(formatted_hobbies)
else:
joint_hobbies = " ".join(formatted_hobbies)
print(f"I like {joint_hobbies}.")
Dari namanya, cukup jelas kira-kira apa yang akan dilakukan: mencetak informasi perkenalan diri berdasarkan parameter first_name
, last_name
, age
, dan hobbies
.
Rekan tim lain tidak akan mikir lagi harus masukkan nilai apa dengan tipe data apa untu masing-masing parameternya.
Untuk first_name
dan last_name
? String.
Untuk age
? Integer. Untuk hobbies
? List of string!
Kita juga bisa langsung tahu bahwa fungsi ini tidak memiliki return value, karena sudah tertera def print_self_introduction(...) -> None:
.
Kalau misal tertera def get_self_introduction(...) -> str:
, kita bisa langsung menebak bahwa fungsi ini akan mengembalikan nilai berupa string.
Tidak akan lagi ada pertanyaan, "Eh ngab... Parameter hobbies
ini perlu kumasukkan apa, ya? String? String yang pake delimiter koma? Name file teks berisi daftar hobi? Atau apa?" Error-pun akan diminimalisir.
Note: saya tidak akan membahas detail type annotation di sini
Gunakan formatter
Saya biasanya menggunakan black untuk melakukan formatting dan isort untuk merapikan bagian import. Selain agar nyaman dipandang, format kode akan menjadi konsisten di setiap bagian proyek. Fix, no debat.
Black
Black bisa membuat kode seperti ini
def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, 'w') as f:
...
menjadi seperti ini
def very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = True,
debug: bool = False,
):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
Atau merapikan call chaining yang panjang menjadi seperti ini
def example(session):
result = (
session.query(models.Customer.id)
.filter(
models.Customer.account_id == account_id,
models.Customer.email == email_address,
)
.order_by(models.Customer.id.asc())
.all()
)
Dan seterusnya. Silakan mengacu ke dokumentasinya.
Isort
Kita bisa dengan mudah merapikan bagian import. Isort akan merapikan kode ini
from my_lib import Object
import os
from my_lib import Object3
from my_lib import Object2
import sys
from third_party import lib15, lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8, lib9, lib10, lib11, lib12, lib13, lib14
import sys
from __future__ import absolute_import
from third_party import lib3
print("Hey")
print("yo")
menjadi seperti ini
from __future__ import absolute_import
import os
import sys
from third_party import (lib1, lib2, lib3, lib4, lib5, lib6, lib7, lib8,
lib9, lib10, lib11, lib12, lib13, lib14, lib15)
from my_lib import Object, Object2, Object3
print("Hey")
print("yo")
Demikian. Selamat mencoba.
Tentu saja tidak ada yang menghalangimu untuk tidak mengikuti standar. Bebas. Santai. Selamat menjadi coder python yang edgy dan melawan arus.