Как избежать использования global в C для обнаружения установки переменной? - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть функция foo (), которая вызывается из двух разных потоков кода.Допустим, эти два потока кода имеют две разные функции, вызывающие foo ()

bar () и tar ()

Я хочу принять какое-то решение на основе какой функции (bar ()или tar ()) вызвал foo ().В настоящее время я устанавливаю глобальную переменную IN_BAR = 1;в случае bar () и IN_BAR = 0;в случае смолы.Затем я проверяю значение "IN_BAR" в foo () и что-то делаю.

int IN_BAR = 0; // global

void bar () {
...
IN_BAR = 1;
foo();
IN_BAR = 0;
..
}

void tar() {
...
foo();
...
}

void foo() {
...
if (IN_BAR)
   do_this();
else
   do_that();
}

В настоящее время есть много мест (в кодовой базе), которые выглядят так.Я хочу избежать использования глобальных переменных и установки и сброса глобальных переменных.Есть ли способ справиться с вышеупомянутой ситуацией?Или здесь есть недостаток дизайна?

РЕДАКТИРОВАТЬ:
Есть случаи, когда bar () и tar () не вызывают напрямую foo (), то есть между ними много вызовов, но решение должнобыть сделано в bar () и tar ().

bar () -> filler1 () -> filler2 () -> foo ()
tar () -> func1 () -> func2 () -> foo ()

test.c

#include <stdio.h>
// globals
int data = 0;
int IN_BAR = 0;

int calculate()
{
    // Some algorithm.
    return 10;
}

void foo()
{
    if (!IN_BAR)
        data = calculate();
    // Use data in this method.
    printf("Data: %d\n", data);
}

// This function is a place holder for multiple functions.
void func_bar()
{
    foo();
}

void bar()
{
    IN_BAR = 1;
    data = calculate();
    func_bar();
    IN_BAR = 0;
}

// This function is a place holder for multiple functions.
void func_tar()
{
    foo();
}

void tar()
{
    func_tar();
}

int main()
{
    int c = 1;
    if (c == 1)
        bar();
    else
        tar();
    return 1;
}

Я вычисляю что-то внутри бара и хочу использовать повторно в foo (), но в случае с tar я не рассчитываю и хочу вычислить заново, так как расчет не производится.

Как я могу обработать этот случай?

Ответы [ 2 ]

0 голосов
/ 27 февраля 2019

Есть ли способ справиться с вышеупомянутой ситуацией?

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

Или здесь есть недостаток дизайна?

Это зависит от обстоятельств.Функция на самом деле не должна знать или беспокоиться о том, откуда она вызывается, но, возможно, есть некоторая информация, относящаяся к вызывающей стороне, которая имеет смысл в качестве ввода.Например, если вы знаете, что bar() вызывается только тогда, когда доступны некоторые данные, а tar() вызывается только тогда, когда данные недоступны, вы можете передать параметр, который указывает, доступны ли данные.Или вы можете просто передать сами данные, если они доступны, или nil, если это не так.

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

Бывают случаи, когда bar() & tar () не вызывают напрямую foo (), т.е. между ними много вызовов, но решение должно быть принято в bar () и tar ().

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

tar () -> func1 () -> func2 () -> foo ()

, так как tar() вызывает func1()вместо foo() он не должен делать никаких предположений о том, как работает func1() - он должен просто передавать информацию, которая необходима func1() для выполнения своей работы, и возвращать результат, который возвращает func1().«Решение», которое tar() и bar() принимают, каким бы оно ни было на самом деле, является для вас необходимым входным значением для foo(), и, следовательно, необходимым входным значением для func2(), и, следовательно, необходимым вкладом для func1(), даже если func1() и func2() не используют эту информацию, кроме как для вызова какой-либо другой функции.Таким образом, один из вариантов состоит в том, чтобы каждая промежуточная функция принимала новый параметр, который они просто передают следующей функции в цепочке.

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

Проблема, с которой вы столкнулисьперед нами процедурная версия проблемы «как получить нужные мне данные», которая часто возникает в объектно-ориентированном программировании.Возможны следующие варианты: 1) сделать так, чтобы объект предположил, где взять необходимые ему данные;или 2) сообщить объекту, где взять необходимые ему данные.Вариант 1 обычно включает одноэлементную или другую глобально доступную порцию данных.Вариант 2 известен как внедрение зависимостей , и его часто называют «скажи, не спрашивай».

Другое решение, которое вы, возможно, не рассматривали, - это разбить foo() на две или более функции, чтобы tar() и bar() в итоге вызывали две разные функции, каждая из которых выполняет свою задачу в нужном направлении.Это может сработать, если цепочки вызовов для tar() и bar() действительно различны.В этом случае вы по-прежнему фактически передаете ту же информацию - вы просто делаете это неявно, используя имя функции.

Давайте попробуем конкретизировать этот совет, посмотрев предоставленный вами код:

void foo()
{
    if (!IN_BAR)
        data = calculate();
    // Use data in this method.
    printf("Data: %d\n", data);
}

Причина, по которой foo() здесь зависит от IN_BAR, заключается в том, что bar() вызывает calculate() и сохраняет результат в data, который является другой глобальной переменной.Если foo() вызывается как часть tar(), то calculate() не был вызван и data, вероятно, не будет иметь полезного значения, поэтому вам нужно вызвать его внутри foo() в этом случае.

Ваш конкретный вопрос, похоже, касается способов избежать необходимости IN_BAR, поэтому давайте сначала рассмотрим это.Одним из решений здесь является просто сделать действительное data требование для вызова foo().Если вы сделаете это, тогда foo() не нужно IN_BAR - он может просто предположить, что data всегда допустимо, потому что вызов его без этого является ошибкой.Он снимает с себя ответственность за вызов calculate() из сферы foo() и позволяет вам звонить calculate() куда угодно выше по цепочке - вы можете сделать это в tar() или func_tar() или в любом другом месте,до тех пор, пока вы делаете это до вызова foo(), а foo() становится проще:

void foo()
{
    // Use data in this method.
    printf("Data: %d\n", data);
}

Если есть какая-то причина, по которой вы не можете этого сделать, то вы можете, по крайней мере, связать действительностьdata к этой одной переменной.Вы можете сделать это, либо определив какое-либо значение для data, что означает «недействительный», так что вы можете просто просмотреть данные и узнать, действительно ли это допустимо.Например, вы могли бы изменить его тип на int* и инициализировать его на nil, а затем calculate() вернуть указатель на int, который он вычисляет:

void foo()
{
    if (data == nil)
        data = calculate();
    // Use data in this method.
    printf("Data: %d\n", data);
}

Это будетгораздо лучше, однако, переопределить foo(), чтобы data передавался явно:

void foo(int data)
{
    // Use data in this method.
    printf("Data: %d\n", data);
}

Теперь вам не нужны ни data, ни IN_BAR как глобальные, а foo()Потребность в значении для data очевидна.Если для foo() вполне нормально вызывать calculate(), когда нет значения data, то, безусловно, должно быть в порядке, чтобы func_tar() вызывал calculate() непосредственно перед вызовом foo():

void func_tar()
{
    foo( calculate() );
}

В случае bar(), который вызывает сам calculate(), у вас уже есть значение data, и оно должно быть передано его вспомогательным функциям:

void func_bar(int data)
{
    foo(data);
}

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

0 голосов
/ 27 февраля 2019

Если вы хотите избежать этого, то не имеет bar вызов foo.Поскольку, очевидно, bar знает о foo, он может вызвать соответствующую версию и оставить это решение вызывающей стороне.

Создать отдельную функцию для случая bar вызовов foo и для tar вызов foo:

void bar () {
    ...
    foobar();
    ..
}

void tar() {
    ...
    footar();
    ...
}

void foobar() {
    ...
    do_this();
}
void footar() {
    ...
    do_that();
}


Редактировать:

Чтобы избежать необходимости вносить много изменений в код, вы можете изменять только там, где в настоящее время используется глобальная переменная (и добавил ДжонатанПредложение Леффлера):

void bar () {
    ...
    foobar(); // change only here
    ..
}

void tar() {
    ...
    foo();
    ...
}

void foobar() {
    foocommon();
    do_this();
}
void foo() {
    foocommon();
    do_that();
}
void foocommon()
{
    ...
}
...