Несколько рекомендаций

Об особенностях языка Python

Основное назначение языка Python: написание сценариев (или скриптов, как их ещё называют) — коротких программ, связывающих другие более сложные программы в единое целое. Одним из следствий этого оказался непродуманный и очень неудачный дизайн языка, сильно затрудняющий программирование чего-либо, отличного от простых сценариев (и, к сожалению, со временем питон становится всё хуже и хуже в плане дизайна).

В этом разделе мы приведём несколько рекомендаций, соблюдая которые можно несколько отложить тот момент, когда программа становится совершенно нечитаемой и немодифицируемой.

Структура программы

В начале программы идут импорты разнообразных модулей.

Не рекомендуется захламлять пространство имён импортами вида

from foobar import *

или даже

from foobar import foo, bar

Если не нравится слишком длинное имя модуля, то гораздо лучше этот модуль переименовать, чем импортировать его функции без префикса:

import sqlite3 as db

Программу рекомендуется составлять из определений констант и функций, придерживаясь следующих пунктов:

  • следует выбрать единую схему именования сущностей и её придерживаться; например, принято имена глобальных переменных писать исключительно заглавными буквами, а имена функций — змеиным регистром (do_such_and_such)
  • не следует пользоваться возможностью изменения привязок глобально объявленных переменных; про конструкции global и nonlocal лучше вообще забыть
  • привязывать мутабельные объекты к глобальным переменным тоже следует с большой осторожностью
  • желательно вне определений функций не иметь никакого кода, кроме, возможно, единственного вызова функции, которая служит точкой входа в программу

Функции

Разбиение программы на функции с чётко очерченными задачами и регламентом использования позволяет обратить длинную последовательность команд (которой, собственно, программа на Python и является) в гораздо более легко воспринимаемую форму. Сравните, например, такую реализацию сортировки «пузырьком»:

n = int(input())

to_sort = []

i = 0
while i < n:
    to_sort.append(int(input()))
    i += 1

i = 0
while i < n:
    j = 0
    while j < n-1:
        if to_sort[j+1] < to_sort[j]:
            to_sort[j], to_sort[j+1] = to_sort[j+1], to_sort[j]
        j += 1
    i += 1

i = 0
while i < n:
    print(to_sort[i], end=' ')
    i += 1

print()

с такой:

def main():
    to_sort = read_array()
    bubble_sort(to_sort)
    print_array(to_sort)
    
def read_array():
    n = read_int()
    return [read_int() for i in range(n)]

def read_int():
    return int(input())

def bubble_sort(array):
    n = len(array)
    for i in range(n): bubble_step(array)
    
def bubble_step(array):
    n = len(array)
    for j in range(n-1):
        if array[j+1] < array[j]: swap(array, j, j+1)

def swap(a, i, j): a[i], a[j] = a[j], a[i]

def print_array(a):
    n = len(a)
    for i in range(n): print(a[i], end = ' ')
    print_newline()
    
def print_newline(): print()

main()

Обратите внимание, что код, разбитый на функции, оказался длиннее, чем не разбитый. Но вот убедиться в том, что он правильно работает, гораздо легче: каждую функцию можно протестировать по отдельности, а сами определения функций читать не труднее, чем текст на естественном языке, описывающий работу алгоритма.

Выделять код в отдельную функцию рекомендуется как минимум в следующих случаях:

  • функция стала слишком длинной: даже 30-40 строк — это уже очень много; в таком случае её код нужно представить в виде нескольких вызовов вспомогательных функций
  • код слишком сильно уехал вправо (например, из-за условия внутри условия или цикла внутри цикла); вообще длинные строчки или же вложенные управляющие конструкции очень трудно читать и понимать их назначение
  • в нескольких местах появись похожие друг на друга участки кода: их следует обобщить, приведя к одинаковому виду, и представить в виде вызовов одной и той же функции
  • полезно писать функции-тесты, содержащие примеры использования других функций и ожидаемые от них результаты; функции-тесты являются одновременно комментарием к функции и некоторой гарантией того, что изменение реализации функции не приведёт к неожиданным изменениям её поведения

Комментарии

При комментировании программы важно помнить, что почти всегда правильный выбор имён и/или удачная функция-тест гораздо полезнее комментария на естественном языке, который:

  • имеет тенденцию устаревать, не успевая за изменениями кода
  • из-за особенностей естественных языков может иметь множество разнообразных интерпретаций

Ни в коем случае не нужно делать комментарии в духе:

a += 3  ## прибавляем тройку

А вот пример более удачного комментария:

x1 = average(distribution, segment) + 3  ## прибавляем тройку ...
x2 = approximate('quadratic', foobar, x1)
x3 = adjust(y, x1, x2)
## ... поскольку в статье [Х. Суньвчай, И. Выньсухим, 1994] так опредён 
## первый шаг аппроксимационного алгоритма

или

foobar(a+3)  ## прибавляем тройку для большей надёжности

Говоря другими словами, комментарий должен отвечать не на вопрос «Что тут происходит?» (на этот вопрос отвечает сам код), а на вопрос «Зачем так происходит?»