Satu standup di Yandex.Taxi, atau Apa yang Anda butuhkan untuk mengajar pengembang backend

Nama saya Oleg Ermakov, saya bekerja di tim pengembangan backend aplikasi Yandex.Taxi. Merupakan kebiasaan bagi kita untuk melakukan stand-up harian di mana kita masing-masing berbicara tentang tugas yang dilakukan pada siang hari. Inilah yang terjadi ...

Nama-nama karyawan dapat berubah, tetapi tugasnya cukup nyata!

Pada pukul 12:45, seluruh tim berkumpul di ruang rapat. Kata pertama diambil oleh Ivan, seorang pengembang trainee.

Ivan:

Saya mengerjakan tugas menampilkan semua opsi yang memungkinkan untuk jumlah yang bisa diberikan penumpang kepada pengemudi dengan biaya perjalanan yang diketahui. Tugas ini cukup terkenal - ini disebut "Pergantian koin." Memperhatikan secara spesifik, dia menambahkan beberapa optimasi pada algoritma. Saya memberi permintaan kolam renang untuk tinjauan sehari sebelum kemarin, tetapi sejak itu saya telah mengoreksi komentar.

Dengan senyum puas Anna, menjadi jelas yang ucapannya dikoreksi oleh Ivan.



Pertama-tama, dia membuat dekomposisi minimum dari algoritma, dan dia dengan cerdas menerima uang kertas. Dalam implementasi pertama, kemungkinan uang kertas terdaftar dalam kode, oleh karena itu, mereka dibawa ke konfigurasi oleh negara.

Menambahkan komentar untuk masa depan, sehingga setiap pembaca dapat dengan cepat mengetahui algoritma:

for exception in self.exceptions[banknote]: exc_value = value + exception.delta if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value) #       for exception in self.exceptions[banknote]: #          #    exc_value = value + exception.delta #           # (corner case) if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value) 

Yah, tentu saja, saya menghabiskan sisa waktu menutupi seluruh kode dengan tes.

 RUB = [1, 2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000] CUSTOM_BANKNOTES = [1, 3, 7, 11] @pytest.mark.parametrize( 'cost, banknotes, expected_changes', [ # no banknotes ( 321, [], [], ), # zero cost ( 0, RUB, [], ), # negative cost ( -13, RUB, [], ), # simple testcase ( 264, RUB, [265, 270, 300, 400, 500, 1000, 2000, 5000], ), # cost bigger than max banknote ( 6120, RUB, [6121, 6150, 6200, 6300, 6500, 7000, 8000, 10000], ), # min cost ( 1, RUB, [2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000], ), ... ], ) 

Selain tes biasa yang berjalan pada setiap pembangunan proyek, ia menulis tes yang menggunakan algoritme tanpa optimisasi (menganggapnya sebagai kesalahan besar). Hasil dari algoritma ini untuk setiap tagihan dari 10 ribu kasus pertama dimasukkan ke dalam file dan dijalankan secara terpisah pada algoritma dengan optimasi untuk memastikan bahwa itu benar-benar berfungsi dengan benar.

Mari luangkan waktu sejenak untuk mengalihkan perhatian dari stand-up dan merangkum hasil lokal dari semua yang dikatakan Ivan. Saat menulis kode, tujuan utamanya adalah memastikan kinerjanya. Untuk mencapai tujuan ini, Anda harus menyelesaikan tugas-tugas berikut:

  • Uraikan logika bisnis menjadi pecahan atom. Keterbacaan menjadi rumit ketika melihat kanvas kode yang ditulis dalam satu fungsi.
  • Tambahkan komentar ke bagian kode yang "sangat kompleks". Tim kami memiliki pendekatan berikut: jika Anda ditanya pertanyaan tentang implementasi pada tinjauan kode (mereka meminta untuk menjelaskan algoritma), maka Anda perlu menambahkan komentar. Lebih baik lagi, pikirkan terlebih dahulu dan tambahkan sendiri.
  • Tulis tes yang mencakup cabang utama dari eksekusi algoritma. Tes tidak hanya metode untuk memverifikasi kesehatan kode. Mereka masih berfungsi sebagai contoh menggunakan modul Anda.

Sayangnya, bahkan spesialis dengan pengalaman bertahun-tahun tidak selalu menggunakan pendekatan ini dalam pekerjaan mereka. Di sekolah pengembangan backend yang kita lakukan sekarang, siswa akan memperoleh keterampilan praktis dalam menulis kode berkualitas tinggi secara arsitektur. Tujuan kami yang lain adalah untuk menyebarluaskan praktik cakupan uji untuk proyek tersebut.

Tapi kembali ke stand-up. Setelah Ivan, Anna berbicara.

Anna:

Saya sedang mengembangkan layanan mikro untuk mengembalikan gambar promosi. Seperti yang Anda ingat, layanan ini pada awalnya memberikan data-stubs statis. Kemudian para penguji meminta untuk menyesuaikannya, dan saya memasukkannya ke dalam konfigurasi, dan sekarang saya melakukan implementasi yang “jujur” dengan mengembalikan data dari basis data (PostgreSQL 10.9). Dekomposisi, awalnya diletakkan, banyak membantu saya, dalam kerangka yang antarmuka untuk menerima data dalam logika bisnis tidak berubah, dan setiap sumber baru (apakah itu konfigurasi, basis data atau layanan eksternal mikro) hanya mengimplementasikan logikanya sendiri.



Saya memeriksa sistem tertulis di bawah beban, pengujian menunjukkan bahwa pegangan mulai rem tajam ketika kita pergi ke database. Menurut penjelasan, saya melihat bahwa indeks tidak digunakan. Sampai saya menemukan cara untuk memperbaikinya.
Vadim:
Dan permintaan seperti apa?
Anya:
Dua kondisi dalam OR:

 SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val' OR table_1.attr2 IN ('val1', 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at 

Kueri menjelaskan menunjukkan bahwa itu tidak menggunakan salah satu indeks untuk atribut attr1 dari table_2 dan attr2 dari table_1.
Vadim:
Menghadapi perilaku serupa di MySQL, masalahnya justru pada kondisi OR, karena hanya satu indeks yang digunakan, katakanlah attr2. Dan kondisi kedua menggunakan pemindaian seq - lulus penuh melalui tabel. Permintaan dapat dibagi menjadi dua permintaan independen. Sebagai opsi, pisahkan dan bekukan hasil kueri di sisi backend. Tetapi kemudian Anda perlu berpikir tentang membungkus dua permintaan ini dalam suatu transaksi, atau menggabungkannya menggunakan UNION - pada kenyataannya, di sisi dasar:

 SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val') AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_1.attr2 IN ('val1' , 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at 
Anya:
Terima kasih, saya akan mencoba ^ _ ^

Untuk meringkas lagi:

  • Hampir semua tugas pengembangan produk terkait dengan mendapatkan catatan dari sumber eksternal (layanan atau database). Anda harus secara hati-hati mendekati masalah dekomposisi kelas yang membongkar data. Kelas yang dirancang dengan benar akan memungkinkan Anda untuk menulis tes dan memodifikasi sumber data tanpa masalah.
  • Untuk bekerja secara efektif dengan database, Anda perlu mengetahui fitur eksekusi permintaan, misalnya, mengerti menjelaskan.

Bekerja dengan informasi dan mengatur aliran data adalah bagian integral dari tugas pengembang backend mana pun. Sekolah akan memperkenalkan arsitektur interaksi layanan (dan sumber data). Siswa akan belajar untuk bekerja dengan database secara arsitektur dan dalam hal operasi - migrasi dan pengujian data.

Yang terakhir berbicara adalah Vadim.

Vadim:

Saya bertugas selama seminggu, memilah urutan insiden. Satu kesalahan konyol dalam kode memakan waktu yang sangat lama: tidak ada log pada permintaan di prod, meskipun kreasi mereka ditulis dalam kode.

Dengan kesunyian yang menyedihkan dari semua yang hadir, jelas - semua orang sudah entah bagaimana menghadapi masalah .

Untuk mendapatkan semua log sebagai bagian dari permintaan, request_id digunakan, yang dilemparkan ke semua catatan dalam bentuk berikut:

 #   request_id logger.info( 'my log msg', ) #   request_id logger.info( 'my log msg', extra=log_extra, #   request_id —     ) 

log_extra adalah kamus dengan meta-informasi dari permintaan, kunci dan nilai yang akan ditulis ke log. Tanpa meneruskan log_extra ke fungsi logging, catatan tidak akan dikaitkan dengan semua log lain, karena tidak akan memiliki request_id.

Saya harus memperbaiki kesalahan dalam layanan, roll-out dan baru kemudian menangani insiden tersebut. Ini bukan pertama kalinya ini terjadi. Untuk mencegah hal ini terjadi lagi, saya mencoba untuk memperbaiki masalah secara global dan menyingkirkan log_extra.

Pertama saya menulis pembungkus atas eksekusi standar dari permintaan:

 async def handle(self, request, handler): log_extra = request['log_extra'] log_extra_manager.set_log_extra(log_extra) return await handler(request) 

Itu perlu untuk memutuskan bagaimana menyimpan log_extra dalam satu permintaan. Ada dua opsi. Yang pertama adalah mengubah task_factory untuk eventloop dari asyncio:

 class LogExtraManager: __init__(self, context: Any, settings: typing.Optional[Dict[str, dict]], activations_parameters: list) -> None: loop = asyncio.get_event_loop() task_factory = loop.get_task_factory() if task_factory is None: task_factory = _default_task_factory @functools.wraps(task_factory) def log_extrad_factory(ev_loop, coro): child_task = task_factory(ev_loop, coro) parent_task = asyncio.Task.current_task(loop=ev_loop) log_extra = getattr(parent_task, LOG_EXTRA_CONTEXT_KEY, None) setattr(child_task, LOG_EXTRA_CONTEXT_KEY, log_extra) return child_task # updating loop, so any created task will # get the log_extra of its parent loop.set_task_factory(log_extrad_factory) def set_log_extra(log_extra: dict): loop = asyncio.get_event_loop() task = asyncio.Task.current_task(loop=loop) setattr(task, LOG_EXTRA_CONTEXT_KEY, log_extra) 

Opsi kedua adalah "mendorong" transisi ke Python 3.7 melalui perintah infrastruktur untuk menggunakan konteksvars :

 log_extra_var = contextvars.ContextVar(LOG_EXTRA_CONTEXT_KEY) class LogExtraManager: def set_log_extra(log_extra: dict): log_extra_var.set(log_extra) 

Yah dan selanjutnya perlu meneruskan disimpan dalam konteks log_extra di logger.

 class LogExtraFactory(logging.LogRecord): # this class allows to create log rows with log_extra in the record def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) task = asyncio.Task.current_task() log_extra = getattr(task, LOG_EXTRA_CONTEXT_KEY, None) if not log_extra: return for key in log_extra: self.__dict__[key] = log_extra[key] logging.setLogRecordFactory(LogExtraFactory) 

Ringkasan:

  • Di Yandex.Taxi (dan di mana-mana di Yandex) asyncio aktif digunakan. Penting tidak hanya untuk dapat menggunakannya, tetapi juga untuk memahami struktur internalnya.
  • Kembangkan kebiasaan membaca changelogs semua versi bahasa baru, pikirkan tentang bagaimana Anda dapat membuat hidup lebih mudah untuk diri sendiri dan kolega Anda dengan bantuan inovasi.
  • Saat bekerja dengan perpustakaan standar, jangan takut untuk merangkak ke dalam kode sumber mereka dan memahami perangkat mereka. Ini adalah keterampilan yang sangat berguna yang akan memungkinkan Anda untuk lebih memahami pengoperasian modul dan membuka kemungkinan baru dalam penerapan fitur.

Para guru sekolah backend makan lebih dari satu pon garam dan mengisi banyak kerucut dalam operasi layanan yang tidak sinkron. Mereka akan memberi tahu siswa tentang fitur operasi asinkron Python - baik di tingkat praktis maupun dalam analisis paket internal.

Buku dan tautan


Mempelajari Python dapat membantu Anda:

  • Tiga buku: Python Cookbook , Menyelam ke Python 3, dan Trik Python .
  • Video ceramah oleh pilar industri TI seperti Raymond Hettinger dan David Beasley. Dari video ceramah yang pertama, laporan "Melampaui PEP 8 - Praktik terbaik untuk kode yang dapat dipahami dengan indah" dapat dibedakan. Beasley menyarankan Anda untuk menonton pertunjukan tentang asyncio.

Untuk mendapatkan pemahaman arsitektur tingkat tinggi, baca buku:

  • "Aplikasi yang sangat dimuat . " Di sini, masalah berinteraksi dengan data dijelaskan secara rinci (pengkodean data, bekerja dengan data terdistribusi, replikasi, partisi, transaksi, dll.).
  • “Layanan Mikro. Pengembangan dan pola refactoring . " Buku ini menunjukkan pendekatan dasar untuk arsitektur layanan mikro, menjelaskan kekurangan dan masalah yang harus dihadapi seseorang ketika beralih dari monolit ke layanan mikro. Hampir tidak ada di pos tentang mereka, tetapi saya tetap menyarankan Anda untuk membaca buku ini. Anda akan mulai memahami tren dalam membangun arsitektur dan mempelajari praktik dasar dekomposisi kode.

Keterampilan terpenting lainnya yang dapat Anda kembangkan tanpa henti dalam diri Anda adalah membaca kode orang lain. Jika Anda tiba-tiba menyadari bahwa Anda jarang membaca kode orang lain, saya menyarankan Anda untuk mengembangkan kebiasaan menonton repositori populer yang baru.

Stand-up berakhir, semua orang pergi bekerja.

Source: https://habr.com/ru/post/id464179/


All Articles