Kerangka Bagian 1 sudah bisa menambah dan menampilkan tugas. Dua operasi tersisa — menandai selesai dan menghapus — justru bagian yang paling mudah salah: keduanya bekerja berdasarkan nomor yang diketik pengguna, dan nomor yang tidak valid tidak boleh membuat program berhenti. Pada bagian ini kita lengkapi kedua operasi tersebut sambil memperketat kehati-hatian dalam membaca dan mencari posisi elemen di dalam list.

Daftar dengan Penanda dan Pesan Kosong #

Fungsi tampilkan_daftar kita rombak menjadi dua hal: tampilkan [x] untuk tugas selesai dan [ ] untuk yang belum, serta beri pesan jelas ketika daftar masih kosong:

python
def tampilkan_daftar(daftar_tugas):
    if not daftar_tugas:
        print("Belum ada tugas.")
        return

    for tugas in daftar_tugas:
        penanda = "[x]" if tugas["selesai"] else "[ ]"
        print(f"{penanda} {tugas['id']}. {tugas['judul']}")

Tiga hal baru terjadi di sini:

  1. if not daftar_tugas:: list kosong bernilai False pada konteks boolean, jadi pemeriksaan ini menangkap kondisi belum ada tugas. Tanpa perlindungan ini, loop for hanya diam dan pengguna tidak tahu apakah program bekerja atau macet.
  2. return dini: begitu pesan kosong tercetak, fungsi berhenti — sisa kode di bawahnya tidak perlu dijalankan. Struktur “periksa kondisi aneh dulu, kerjakan kasus normal setelahnya” menjaga indentasi tetap dangkal.
  3. "[x]" if tugas["selesai"] else "[ ]": conditional expression memilih penanda dalam satu baris. Nilai tugas["selesai"] selalu True atau False karena kita yang membuat datanya — Bagian 4 nanti akan memvalidasi kenyataan itu ketika data datang dari luar.

Membaca Nomor dengan Aman #

Kedua operasi baru meminta satu angka dari pengguna. Masalahnya, input() selalu mengembalikan str, dan int("abc") membangkitkan ValueError:

python
def baca_nomor():
    try:
        return int(input("Nomor tugas: ").strip())
    except ValueError:
        print("Nomor tugas harus berupa angka.")
        return None

Alurnya: fungsi int() mencoba mengubah hasil input() menjadi bilangan bulat. Ketika teks bukan angka, interpreter Python membangkitkan ValueError, blok except menangkapnya, dan fungsi mengembalikan None sebagai tanda “tidak ada nomor yang valid”. Pemanggil kemudian memeriksa if nomor is not None sebelum melanjutkan — pola try/except ini dibahas tuntas pada materi try dan except.

Menandai Selesai dan Menghapus #

Kedua operasi mencari tugas berdasarkan id, bukan posisi dalam list. Nomor yang tampil di layar adalah id tugas, sehingga nomor tersebut tidak pernah bergeser ketika tugas lain dihapus:

python
def tandai_selesai(daftar_tugas, nomor):
    for tugas in daftar_tugas:
        if tugas["id"] == nomor:
            tugas["selesai"] = True
            print(f"Tugas {nomor} ditandai selesai.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False


def hapus_tugas(daftar_tugas, nomor):
    for indeks, tugas in enumerate(daftar_tugas):
        if tugas["id"] == nomor:
            dihapus = daftar_tugas.pop(indeks)
            print(f"Tugas \"{dihapus['judul']}\" dihapus.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False

Perhatikan tiga keputusan pentingnya:

  1. return True / return False: fungsi melaporkan apakah operasi benar-benar mengubah data. Bagian 3 akan memakai nilai kembalian ini untuk memutuskan kapan berkas penyimpanan perlu ditulis ulang.
  2. enumerate(daftar_tugas): fungsi hapus_tugas butuh indeks karena method pop(indeks) menghapus berdasarkan posisi, bukan nilai. enumerate() menyediakan pasangan (indeks, elemen) sekaligus — materi enumerate() membahasnya lebih lanjut.
  3. Menghapus lewat pop(indeks) setelah loop menemukan target: menghapus elemen di tengah iterasi list yang sama berisiko melewatkan elemen berikutnya. Di sini pop() dipanggil tepat sebelum return True, sehingga loop langsung berhenti dan tidak melanjutkan iterasi pada list yang telah berubah.

Nomor yang Tidak Dipakai Ulang #

Perhitungan len(daftar_tugas) + 1 dari Bagian 1 kini rusak: setelah Beli susu (id 2) dihapus dari tiga tugas, panjang list tinggal dua dan angka 3 akan dipakai ulang — padahal Kirim laporan bulanan sudah memegang id 3. Solusinya, hitung nomor baru dari id terbesar yang masih ada:

python
def nomor_id_baru(daftar_tugas):
    if not daftar_tugas:
        return 1
    return max(tugas["id"] for tugas in daftar_tugas) + 1

Guard if not daftar_tugas: return 1 wajib ada karena max() pada sequence kosong membangkitkan ValueError. Setelah fungsi ini dipasang, tambah_tugas memanggilnya menggantikan len(daftar_tugas) + 1.

Program Lengkap #

Berikut main.py setelah seluruh perubahan Bagian 2. Dua cabang menu yang tadinya placeholder kini benar-benar bekerja — berkas yang sama juga tersedia pada bagian Berkas Sumber di bawah halaman:

python
"""Pengelola Daftar Tugas — program manajemen tugas berbasis terminal."""


def tampilkan_menu():
    print("=== Pengelola Daftar Tugas ===")
    print("1. Tambah tugas")
    print("2. Daftar tugas")
    print("3. Tandai selesai")
    print("4. Hapus tugas")
    print("5. Keluar")


def nomor_id_baru(daftar_tugas):
    if not daftar_tugas:
        return 1
    return max(tugas["id"] for tugas in daftar_tugas) + 1


def tambah_tugas(daftar_tugas, judul):
    id_baru = nomor_id_baru(daftar_tugas)
    tugas_baru = {"id": id_baru, "judul": judul, "selesai": False}
    daftar_tugas.append(tugas_baru)
    print(f"Tugas \"{judul}\" tersimpan dengan id {id_baru}.")


def tampilkan_daftar(daftar_tugas):
    if not daftar_tugas:
        print("Belum ada tugas.")
        return

    for tugas in daftar_tugas:
        penanda = "[x]" if tugas["selesai"] else "[ ]"
        print(f"{penanda} {tugas['id']}. {tugas['judul']}")


def baca_nomor():
    try:
        return int(input("Nomor tugas: ").strip())
    except ValueError:
        print("Nomor tugas harus berupa angka.")
        return None


def tandai_selesai(daftar_tugas, nomor):
    for tugas in daftar_tugas:
        if tugas["id"] == nomor:
            tugas["selesai"] = True
            print(f"Tugas {nomor} ditandai selesai.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False


def hapus_tugas(daftar_tugas, nomor):
    for indeks, tugas in enumerate(daftar_tugas):
        if tugas["id"] == nomor:
            dihapus = daftar_tugas.pop(indeks)
            print(f"Tugas \"{dihapus['judul']}\" dihapus.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False


def main():
    daftar_tugas = []

    while True:
        tampilkan_menu()
        pilihan = input("Pilihan: ").strip()

        if pilihan == "1":
            judul = input("Judul tugas: ").strip()
            if judul:
                tambah_tugas(daftar_tugas, judul)
            else:
                print("Judul tugas tidak boleh kosong.")
        elif pilihan == "2":
            tampilkan_daftar(daftar_tugas)
        elif pilihan == "3":
            nomor = baca_nomor()
            if nomor is not None:
                tandai_selesai(daftar_tugas, nomor)
        elif pilihan == "4":
            nomor = baca_nomor()
            if nomor is not None:
                hapus_tugas(daftar_tugas, nomor)
        elif pilihan == "5":
            print("Sampai jumpa!")
            break
        else:
            print("Pilihan tidak dikenal.")


if __name__ == "__main__":
    main()

Cabang 3 dan 4 kini membaca nomor lewat baca_nomor(), dan nilai None dilewati dengan pemeriksaan if nomor is not None — masukan salah hanya membuahkan pesan, bukan kegagalan program.

Sesi Uji #

Sesi berikut menyentuh semua jalur: operasi berhasil, nomor yang tidak ada, nomor yang bukan angka, penghapusan, lalu penambahan baru:

bash
echo "1
Belajar Python dasar
1
Beli susu
1
Kirim laporan bulanan
3
1
3
9
3
abc
4
2
1
Telepon bengkel
2
5" | python3 main.py

Output yang Dihasilkan:

text
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Belajar Python dasar" tersimpan dengan id 1.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Beli susu" tersimpan dengan id 2.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Kirim laporan bulanan" tersimpan dengan id 3.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas 1 ditandai selesai.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas dengan nomor 9 tidak ditemukan.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Nomor tugas harus berupa angka.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Nomor tugas: Tugas "Beli susu" dihapus.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Judul tugas: Tugas "Telepon bengkel" tersimpan dengan id 4.
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: [x] 1. Belajar Python dasar
[ ] 3. Kirim laporan bulanan
[ ] 4. Telepon bengkel
=== Pengelola Daftar Tugas ===
1. Tambah tugas
2. Daftar tugas
3. Tandai selesai
4. Hapus tugas
5. Keluar
Pilihan: Sampai jumpa!

Perhatikan tiga momen penting pada keluaran di atas: tugas id 1 tampil dengan penanda [x], nomor 9 dan abc hanya membuahkan pesan tanpa menghentikan program, dan tugas baru setelah penghapusan mendapat id 4 — id 2 yang telah dihapus tidak dipakai ulang karena nomor_id_baru menghitung dari id terbesar yang tersisa.

Empat operasi kini lengkap, tetapi data masih lenyap begitu program berakhir. Bagian 3 memindahkan penyimpanan ke berkas JSON agar daftar tugas bertahan antar eksekusi.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

main.py

Unduh berkas
python
"""Pengelola Daftar Tugas — program manajemen tugas berbasis terminal."""


def tampilkan_menu():
    print("=== Pengelola Daftar Tugas ===")
    print("1. Tambah tugas")
    print("2. Daftar tugas")
    print("3. Tandai selesai")
    print("4. Hapus tugas")
    print("5. Keluar")


def nomor_id_baru(daftar_tugas):
    if not daftar_tugas:
        return 1
    return max(tugas["id"] for tugas in daftar_tugas) + 1


def tambah_tugas(daftar_tugas, judul):
    id_baru = nomor_id_baru(daftar_tugas)
    tugas_baru = {"id": id_baru, "judul": judul, "selesai": False}
    daftar_tugas.append(tugas_baru)
    print(f"Tugas \"{judul}\" tersimpan dengan id {id_baru}.")


def tampilkan_daftar(daftar_tugas):
    if not daftar_tugas:
        print("Belum ada tugas.")
        return

    for tugas in daftar_tugas:
        penanda = "[x]" if tugas["selesai"] else "[ ]"
        print(f"{penanda} {tugas['id']}. {tugas['judul']}")


def baca_nomor():
    try:
        return int(input("Nomor tugas: ").strip())
    except ValueError:
        print("Nomor tugas harus berupa angka.")
        return None


def tandai_selesai(daftar_tugas, nomor):
    for tugas in daftar_tugas:
        if tugas["id"] == nomor:
            tugas["selesai"] = True
            print(f"Tugas {nomor} ditandai selesai.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False


def hapus_tugas(daftar_tugas, nomor):
    for indeks, tugas in enumerate(daftar_tugas):
        if tugas["id"] == nomor:
            dihapus = daftar_tugas.pop(indeks)
            print(f"Tugas \"{dihapus['judul']}\" dihapus.")
            return True

    print(f"Tugas dengan nomor {nomor} tidak ditemukan.")
    return False


def main():
    daftar_tugas = []

    while True:
        tampilkan_menu()
        pilihan = input("Pilihan: ").strip()

        if pilihan == "1":
            judul = input("Judul tugas: ").strip()
            if judul:
                tambah_tugas(daftar_tugas, judul)
            else:
                print("Judul tugas tidak boleh kosong.")
        elif pilihan == "2":
            tampilkan_daftar(daftar_tugas)
        elif pilihan == "3":
            nomor = baca_nomor()
            if nomor is not None:
                tandai_selesai(daftar_tugas, nomor)
        elif pilihan == "4":
            nomor = baca_nomor()
            if nomor is not None:
                hapus_tugas(daftar_tugas, nomor)
        elif pilihan == "5":
            print("Sampai jumpa!")
            break
        else:
            print("Pilihan tidak dikenal.")


if __name__ == "__main__":
    main()