Как переменная вводит состояние?

11

Я читал «Стандарты кодирования C ++», и эта строка была там:

Переменные вводят состояние, и вы должны иметь дело с как можно меньшим состоянием, с минимальным временем жизни.

Разве что-то, что мутирует, в конечном итоге не манипулирует государством? Что вам следует иметь дело с как можно меньшим состоянием государства ?

На нечистом языке, таком как C ++, разве управление состоянием на самом деле не то, что вы делаете? И каковы другие способы борьбы с как можно меньшим состоянием, кроме ограничения времени жизни переменной?

kunj2aaan
источник

Ответы:

16

Разве изменчивая вещь не манипулирует государством?

Да.

И что означает «вам нужно иметь дело с маленьким государством»?

Это означает, что чем меньше состояние, тем лучше, чем больше. Больше государства имеет тенденцию вводить больше сложности.

На нечистом языке, таком как C ++, разве управление состоянием не является тем, чем вы занимаетесь?

Да.

Каковы другие способы «справиться с небольшим состоянием», кроме ограничения времени жизни переменной?

Минимизируйте количество переменных. Изолируйте код, который манипулирует каким-либо состоянием, в отдельный модуль, чтобы другие разделы кода могли его игнорировать.

Дэвид Шварц
источник
9

Разве изменчивая вещь не манипулирует государством?

Да. В C ++ единственными изменчивыми вещами являются (не const) переменные.

И что означает «вам нужно иметь дело с маленьким государством»?

Чем меньше у программы состояния, тем легче понять, что она делает. Таким образом, вы не должны вводить состояние, в котором нет необходимости, и не должны сохранять его, если оно вам больше не нужно.

На нечистом языке, таком как C ++, разве управление состоянием не является тем, чем вы занимаетесь?

В мультипарадигмальном языке, таком как C ++, часто есть выбор между «чистым» функционалом или подходом, управляемым состоянием, или каким-то гибридом. Исторически языковая поддержка функционального программирования была довольно слабой по сравнению с некоторыми языками, но она улучшается.

Каковы другие способы «справиться с небольшим состоянием», кроме ограничения времени жизни переменной?

Ограничить область применения, а также срок службы, чтобы уменьшить связь между объектами; предпочитайте локальные, а не глобальные переменные, и частные, а не открытые члены объекта.

Майк Сеймур
источник
5

Состояние означает, что что-то хранится где-то, так что вы можете обратиться к нему позже.

Создание переменной создает пространство для хранения данных. Эти данные - состояние вашей программы.

Вы используете его, чтобы что-то делать, изменять, вычислять и т. Д.

Это состояние , тогда как то, что вы делаете , не является состоянием.

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

В C ++ вы можете создавать объекты функций , которые являются structили classтипами, которые operator()()перегружены. Эти функциональные объекты могут иметь локальное состояние, хотя это не обязательно используется другим кодом в вашей программе. Функторы (т.е. функциональные объекты) очень легко обойти. Это настолько близко, насколько вы можете имитировать функциональную парадигму в C ++. (НАСКОЛЬКО МНЕ ИЗВЕСТНО)

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

Тони Лев
источник
2

Другие дали хорошие ответы на первые 3 вопроса.

И каковы другие способы «справиться с как можно меньшим состоянием», кроме ограничения времени жизни переменной?

Ключевой ответ на вопрос № 1 - да, все, что мутирует, в конечном итоге влияет на состояние. Ключ в том, чтобы не мутировать вещи. Неизменяемые типы, использующие стиль функционального программирования, в котором результат одной функции передается напрямую другой и не сохраняется, передавая сообщения или события напрямую, а не сохраняя состояние, вычисляя значения, а не сохраняя и обновляя их ...

В противном случае вы останетесь с ограничением воздействия государства; либо через видимость или жизнь.

Telastyn
источник
1

И что означает «вам нужно иметь дело с маленьким государством»?

Это означает, что ваши классы должны быть как можно меньше, оптимально представляя одну абстракцию. Если вы поместили 10 переменных в ваш класс, скорее всего, вы делаете что-то не так, и должны увидеть, как провести рефакторинг вашего класса.

BЈовић
источник
1

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

Если вы когда-либо работали с программой, которая имела большое количество глобальных переменных, вы бы поняли это неявно.

Марк Рэнсом
источник
1

Государство просто хранит данные. Каждая переменная действительно является своего рода состоянием, но мы обычно используем «состояние» для ссылки на данные, которые являются постоянными между операциями. В качестве простого, бессмысленного примера у вас может быть класс, который внутренне хранит intи имеет increment()и decrement()выполняет функции-члены. Здесь внутренним значением является состояние, потому что оно сохраняется в течение жизни экземпляра этого класса. Другими словами, значение - это состояние объекта.

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

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

Джозеф Мэнсфилд
источник
1

Разве что-то, что мутирует, в конечном итоге не манипулирует государством?

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

Что вам следует иметь дело с как можно меньшим состоянием государства?

С точки зрения переменной: к ней должно быть как можно меньше строк кода. Сузьте область действия переменной до минимума.

С точки зрения кода: из этой строки кода должно быть доступно как можно меньше переменных. Узкие количество переменных , что строка кода может возможно доступ (это даже не важно , что много ли это делает доступ к нему, все , что имеет значение , является ли это может ).

Глобальные переменные настолько плохи, потому что имеют максимальную область видимости. Даже если они доступны из 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 миллионов строк кода, но она может очень сильно помочь в рассуждениях о правильности вашего программного обеспечения и обнаружении того, что ваши изменения дают предсказуемые результаты, когда вы значительно ограничиваете / ограничиваете количество мест, которые могут видоизменять критические состояния, вокруг которых вращается вся архитектура для правильного функционирования.

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

Государственный Сложность

Даже сложность состояния является довольно важным фактором, который необходимо принимать во внимание. Простая структура, широко доступная за абстрактным интерфейсом, не так сложна.

Сложная структура данных графа, которая представляет основное логическое представление сложной архитектуры, довольно легко испортить и таким образом, что она даже не нарушает инварианты графа. Граф во много раз сложнее, чем простая структура, и поэтому в таком случае становится еще более важным уменьшить воспринимаемую сложность кодовой базы, чтобы уменьшить количество мест, имеющих доступ к такой структуре графа, до абсолютного минимума, и где такая стратегия «центрального обновления», которая превращается в парадигму тяги, чтобы избежать спорадических, прямых толчков к структуре данных графа повсюду может окупиться.


источник