Действие на расстоянии.
Действие на расстоянии — антипаттерн, при котором код в одной части программы незаметно меняет поведение другой, далёкой и не связанной с ним части.
Действие на расстоянии — это антипаттерн в информатике, при котором поведение в одной части программы резко меняется из-за трудно- или невозможно идентифицируемых операций в другой её части.
Способ избежать проблем, связанных с действием на расстоянии, — это грамотное проектирование, которое исключает глобальные переменные и изменяет данные только контролируемым и локальным образом, либо использование стиля чисто функционального программирования со ссылочной прозрачностью.
Термин заимствован из физики, где действием на расстоянии называют взаимодействие тел без какого-либо видимого посредника.
Программные ошибки, вызванные действием на расстоянии, могут возникать оттого, что компонент программы делает что-то не вовремя или влияет на то, на что не должен. Однако очень трудно отследить, какой именно компонент за это отвечает. Побочные эффекты от безобидных действий могут привести программу в неизвестное состояние, так что локальные данные не обязательно остаются локальными. Решение в этом конкретном случае — определить, какие компоненты должны взаимодействовать с какими. Грамотное проектирование, которое точно определяет интерфейс между частями программы и исключает разделяемое состояние, способно во многом устранить проблемы, вызванные действием на расстоянии.
Исторический пример: $[ в Perl
Этот пример на языке программирования Perl демонстрирует особенно серьёзный случай действия на расстоянии (обратите внимание, что переменная $[ была объявлена устаревшей в более поздних версиях Perl):
Индексы массива обычно начинаются с 0, потому что значение
$[обычно равно 0; если установить$[в 1, то массивы начинаются с 1, что радует программистов на Fortran, и поэтому мы видим подобные примеры на man-страницеperl(3):foreach $num ($[ .. $#entry) { print " $num\t'",$entry[$num],"'\n"; }И, конечно, можно установить
$[в 17, чтобы массивы начинались с какого-нибудь случайного числа вроде 17 или 4 вместо 0 или 1. Это был отличный способ навредить авторам модулей.К счастью, здравый смысл возобладал. Сейчас эти возможности признаны ошибками. В рассылке perl5-porters появилось крылатое выражение для таких возможностей: их называют «действием на расстоянии». Принцип таков: объявление в одной части программы не должно радикально и незаметно менять поведение какой-то другой её части.
— Марк Джейсон Доминус, Sins of Perl Revisited
Действие на расстоянии между объектами
Грамотное объектно-ориентированное программирование предполагает принципы проектирования, которые исключают действие на расстоянии.
Закон Деметры гласит, что объект должен взаимодействовать только с близкими к себе объектами. Если требуется действие в удалённой части системы, оно должно реализовываться через распространение сообщения. Грамотное проектирование сильно ограничивает случаи действия на расстоянии, способствуя сопровождаемости программ. Стремление создать объектную оргию возникает из плохого проектирования интерфейсов, возможно, в форме божественного объекта, отсутствия настоящих объектов или пренебрежения законом Деметры.
Одно из преимуществ функционального программирования в том, что действие на расстоянии в нём отходит на второй план, иногда вплоть до того, что его вообще невозможно выразить в исходном языке.
Осознание опасности допущения действия на расстоянии в проекте и умение распознавать его наличие полезны при разработке программ, которые корректны, надёжны и сопровождаемы. Учитывая, что бо́льшая часть расходов на программу может приходиться на фазу сопровождения, а действие на расстоянии делает сопровождение трудным, дорогим и подверженным ошибкам, стоит приложить усилия на этапе проектирования, чтобы его избежать.
Пример
Здесь модуль отчётов меняет общий изменяемый словарь настроек. В invoices.py не меняется ни строчки, но после первого экспорта в CSV все счета вдруг печатаются с четырьмя знаками после запятой.
# settings.py
DEFAULTS = {"currency": "USD", "precision": 2}
# invoices.py
from settings import DEFAULTS
def format_amount(amount):
return f"{amount:.{DEFAULTS['precision']}f} {DEFAULTS['currency']}"
# reports.py
from invoices import format_amount
from settings import DEFAULTS
def export_csv(rows):
DEFAULTS["precision"] = 4 # заодно меняются все счета, напечатанные после этого
return [format_amount(r) for r in rows]
Теперь формат — неизменяемое значение, которое передаётся явно:
from dataclasses import dataclass
@dataclass(frozen=True)
class Format:
currency: str = "USD"
precision: int = 2
def format_amount(amount, fmt=Format()):
return f"{amount:.{fmt.precision}f} {fmt.currency}"
def export_csv(rows):
fmt = Format(precision=4) # влияет только на этот экспорт
return [format_amount(r, fmt) for r in rows]
Изменение в одном месте больше не может просочиться в другое. Общие изменяемые глобальные данные вроде DEFAULTS в теории связанности называют общей связанностью (см. Сильная связанность).
Адаптировано по статье Википедии “Action at a distance (computer programming)” (версия 1329485461), распространяется по лицензии CC BY-SA 4.0; текст переведён и отредактирован, добавлены примеры.