Разве что-то, что мутирует, в конечном итоге не манипулирует государством?
Да, но если она стоит за функцией-членом небольшого класса, который является единственным объектом во всей системе, который может манипулировать своим частным состоянием, то это состояние имеет очень узкую область действия.
Что вам следует иметь дело с как можно меньшим состоянием государства?
С точки зрения переменной: к ней должно быть как можно меньше строк кода. Сузьте область действия переменной до минимума.
С точки зрения кода: из этой строки кода должно быть доступно как можно меньше переменных. Узкие количество переменных , что строка кода может возможно доступ (это даже не важно , что много ли это делает доступ к нему, все , что имеет значение , является ли это может ).
Глобальные переменные настолько плохи, потому что имеют максимальную область видимости. Даже если они доступны из 2 строк кода в кодовой базе, из строки кода POV всегда доступна глобальная переменная. Из POV переменной глобальная переменная с внешней связью доступна для каждой отдельной строки кода во всей кодовой базе (или для каждой отдельной строки кода, которая в любом случае включает заголовок). Несмотря на то, что на самом деле к нему обращаются только 2 строки кода, если глобальная переменная видима для 400 000 строк кода, ваш непосредственный список подозреваемых, когда вы обнаружите, что он был установлен в недопустимое состояние, будет иметь 400 000 записей (возможно, быстро уменьшится до 2 записи с инструментами, но, тем не менее, в ближайшем списке будет 400 000 подозреваемых, и это не является обнадеживающей отправной точкой).
Вероятно также, что даже если глобальная переменная начинает изменяться только двумя строками кода во всей кодовой базе, неудачная тенденция развития кодовых баз в обратном направлении будет иметь тенденцию к значительному увеличению этого числа просто потому, что она может увеличиваться до разработчики, отчаянно стараясь уложиться в сроки, видят эту глобальную переменную и понимают, что могут воспользоваться ею.
На нечистом языке, таком как C ++, разве управление состоянием на самом деле не то, что вы делаете?
В целом, да, если только вы не используете C ++ очень экзотическим способом, который заставляет вас иметь дело с нестандартными неизменяемыми структурами данных и чисто функциональным программированием, - это также часто является источником большинства ошибок, когда управление состоянием становится сложным, а сложность часто является функцией видимости / воздействия этого состояния.
И каковы другие способы борьбы с как можно меньшим состоянием, кроме ограничения времени жизни переменной?
Все они находятся в области ограничения области действия переменной, но есть много способов сделать это:
- Избегайте необработанных глобальных переменных, таких как чума. Даже некоторая тупая глобальная функция setter / getter резко сужает видимость этой переменной и, по крайней мере, позволяет каким-то образом поддерживать инварианты (например, если глобальной переменной никогда не следует разрешать принимать отрицательное значение, установщик может поддерживать этот инвариант). Конечно, даже дизайн сеттера / геттера поверх того, что в противном случае было бы глобальной переменной, довольно плохой дизайн, я хочу сказать, что он все еще намного лучше.
- Сделайте ваши классы меньше, когда это возможно. Класс с сотнями функций-членов, 20 переменных-членов и 30 000 строк кода, реализующих его, будет иметь довольно «глобальные» частные переменные, поскольку все эти переменные будут доступны для его функций-членов, которые состоят из 30 тыс. Строк кода. Вы можете сказать, что «сложность состояния» в этом случае при дисконтировании локальных переменных в каждой функции-члене такова
30,000*20=600,000
. Если бы было доступно 10 глобальных переменных, то сложность состояния могла бы быть такой 30,000*(20+10)=900,000
. Здоровая «сложность состояния» (мой личный вид изобретенной метрики) должна быть в тысячах или ниже для классов, а не для десятков тысяч и определенно не для сотен тысяч. Для бесплатных функций, скажем, сотни или ниже, прежде чем мы начнем получать серьезные головные боли при обслуживании.
- В том же духе, что и выше, не реализуйте что-либо в качестве функции-члена или функции-друга, которая в противном случае может быть не членом, а не-другом, используя только открытый интерфейс класса. Такие функции не могут получить доступ к закрытым переменным класса и, таким образом, уменьшают вероятность ошибки, уменьшая область действия этих закрытых переменных.
- Избегайте объявления переменных задолго до того, как они действительно необходимы в функции (то есть избегайте устаревшего стиля C, который объявляет все переменные в верхней части функции, даже если они нужны только на много строк ниже) Если вы все равно используете этот стиль, по крайней мере, стремитесь к более коротким функциям.
Помимо переменных: побочные эффекты
Многие из этих рекомендаций, которые я перечислил выше, касаются прямого доступа к необработанному, изменяемому состоянию (переменным). Однако в достаточно сложной кодовой базе простого сужения области действия необработанных переменных будет недостаточно, чтобы легко рассуждать о корректности.
Вы могли бы иметь, скажем, центральную структуру данных за полностью твердым, абстрактным интерфейсом, полностью способным идеально поддерживать инварианты, и все равно в конечном итоге столкнуться с большим горем из-за широкого раскрытия этого центрального состояния. Примером центрального состояния, которое не обязательно доступно глобально, но просто широко доступно, является граф центральной сцены игрового движка или структура данных центрального слоя Photoshop.
В таких случаях идея «состояния» выходит за рамки необработанных переменных и касается только структур данных и тому подобного. Это также помогает уменьшить их область (уменьшить количество строк, которые могут вызывать функции, которые косвенно изменяют их).
Обратите внимание, как я намеренно пометил даже интерфейс красным здесь, так как на широком, уменьшенном архитектурном уровне доступ к этому интерфейсу все еще находится в состоянии изменения, хотя и косвенно. Класс может поддерживать инварианты в результате интерфейса, но это заходит так далеко с точки зрения нашей способности рассуждать о правильности.
В этом случае центральная структура данных находится за абстрактным интерфейсом, который может даже не быть глобально доступным. Он может быть просто внедрен, а затем косвенно мутирован (через функции-члены) из множества функций в вашей сложной кодовой базе.
В таком случае, даже если структура данных полностью поддерживает свои собственные инварианты, на более широком уровне могут происходить странные вещи (например, аудиоплеер может поддерживать все виды инвариантов, например, уровень громкости никогда не выходит за пределы диапазона от 0% до 100%, но это не защищает его от нажатия пользователем кнопки воспроизведения и наличия случайного аудиоклипа, отличного от того, который он недавно загрузил, запускается воспроизведение в качестве события, что приводит к корректному перестановке списка воспроизведения, но все еще нежелательное, глючное поведение с широкой точки зрения пользователя).
Способ защитить себя в этих сложных сценариях состоит в том, чтобы «узкое место» в тех местах кодовой базы, которые могут вызывать функции, которые в конечном итоге вызывают внешние побочные эффекты даже из такого рода более широкого представления о системе, которое выходит за рамки простого состояния и выходит за рамки интерфейсов.
Как бы странно это ни выглядело, вы можете видеть, что ни одно «состояние» (показанное красным цветом, и это не означает «необработанную переменную», это просто означает «объект» и, возможно, даже за абстрактным интерфейсом), не доступно из множества мест , Каждая из функций имеет доступ к локальному состоянию, которое также доступно центральному средству обновления, а центральное состояние доступно только центральному средству обновления (что делает его более не центральным, а скорее локальным по своей природе).
Это только для действительно сложных кодовых баз, таких как игра, которая занимает 10 миллионов строк кода, но она может очень сильно помочь в рассуждениях о правильности вашего программного обеспечения и обнаружении того, что ваши изменения дают предсказуемые результаты, когда вы значительно ограничиваете / ограничиваете количество мест, которые могут видоизменять критические состояния, вокруг которых вращается вся архитектура для правильного функционирования.
Помимо необработанных переменных есть внешние побочные эффекты, а внешние побочные эффекты являются источником ошибки, даже если они ограничены несколькими функциями-членами. Если множество функций может напрямую вызывать эти несколько функций-членов, то в системе имеется множество функций, которые могут косвенно вызывать внешние побочные эффекты, что повышает сложность. Если в кодовой базе есть только одно место, которое имеет доступ к этим функциям-членам, и что один путь выполнения не инициируется случайными событиями повсеместно, а вместо этого выполняется очень контролируемым, предсказуемым образом, то это снижает сложность.
Государственный Сложность
Даже сложность состояния является довольно важным фактором, который необходимо принимать во внимание. Простая структура, широко доступная за абстрактным интерфейсом, не так сложна.
Сложная структура данных графа, которая представляет основное логическое представление сложной архитектуры, довольно легко испортить и таким образом, что она даже не нарушает инварианты графа. Граф во много раз сложнее, чем простая структура, и поэтому в таком случае становится еще более важным уменьшить воспринимаемую сложность кодовой базы, чтобы уменьшить количество мест, имеющих доступ к такой структуре графа, до абсолютного минимума, и где такая стратегия «центрального обновления», которая превращается в парадигму тяги, чтобы избежать спорадических, прямых толчков к структуре данных графа повсюду может окупиться.