Законно ли писать программу из процедур без in/out параметров, которые оперируют глобальными переменными?
Столкнулся с программой написанной на мой неопытный взгляд в странном стиле, а именно:
Почти все действия выполняет большое количество процедур не умеющих IN/OUT параметров. При этом они оперируют глобальными параметрами. В программе уже 4200 строк.
Упрощено программу можно представить следующим образом:
int a;
int b;
int result;
int c = 6;
void Click1() {
proc1();
proc2();
proc3();
}
void Click2() {
proc1();
proc2();
proc4();
}
void proc1() {a = 3;}
void proc2() {b = 5;}
void proc3() {result = a + b + c; Console.Write(result)}
void proc4() {result = a * b / c; Console.Write(result)}
Мне предстоит развивать эту программу и меня интересует вопрос - допустимо ли писать программы таким образом или, я должен приложить усилия, чтобы ее изменить или как минимум не повторять этот стиль в новых блоках кода?
На мой взгляд, программа должна выглядеть следующим образом:
int C = 6;
void Click1() {
proc1(int out a);
proc2(int out b);
proc3(a, b, C);
}
void Click2() {
proc1(int out a);
proc2(int out b);
proc4(a, b, C);
}
void proc1(int out a) {a = 3;}
void proc2(int out b) {b = 5;}
void proc3(int a, int b, C) {
int result;
result = a + b + С;
Console.Write(result)
}
void proc4(int a, int b, C) {
int result;
result = a * b / С;
Console.Write(result)
}
Ответы (4 шт):
Лучше не использовать глобальные переменные - их могут изменить и нет способа это узнать. В целом про глобальные переменные много статей, где сказано, что от них много проблем, хотя в начале проекта есть соблазн их использовать. Допускается аккуратно использовать глобальные константы(возможно, есть смысл хранить их в конфиг файле)
Улучшает ли ситуацию второй вариант или как написал коментатор выше, второй код ничем не лучше? И что тогда с ним не так?
out здесь вообще не нужен. Этот пример кода был непрезентабелен. Привести его в порядок можно например вот так:
int C = 6;
void Click1()
{
int a = proc1();
int b = proc2();
proc3(a, b);
}
void Click2()
{
int a = proc1();
int b = proc2();
proc4(a, b);
}
int proc1() { return 3; }
int proc2() { return 5; }
void proc3(int a, int b)
{
int result = a + b + C;
Console.Write(result)
}
void proc4(int a, int b)
{
int result = a * b / C;
Console.Write(result)
}
Использовать поля класса в методах можно и даже нужно, но с умом. Чем больше логическая изоляция блоков кода, тем легче будет потом это всё переписывать, улучшать и т.д.
А понятия "глобальные переменные" в C# нет, есть только локальные.
По поводу реальных примеров out, то есть 2 варианта логики - когда возвращаемые результаты логически нужны для разных целей, и когда они являются однородной группой.
Например метод попытки целочисленного деления, чтобы избежать исключения. Один результат метода требуется для условной конструкции, второй для работы с вычисленным результатом, то есть в разных местах.
bool TryDivide(int a, int b, out int result)
{
if (b != 0)
{
result = a / b;
return true;
}
result = default;
return false;
}
if (TryDivide(6, 3, out int number))
{
Console.WriteLine(number);
}
else
{
Console.WriteLine("деление не удалось");
}
Кстати, про то, как правильно работать с исключениями, я писал здесь.
А когда результат - группа значений, их можно объединить в класс, структуру или кортеж и вернуть через возвращаемое значение.
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Point GetPoint(int x, int y)
{
return new Point(x, y);
}
Point p = GetPoint(1, 2);
Console.WriteLine(p.X);
Console.WriteLine(p.Y);
Это всего-лишь примеры хорошего кода, на практике автор приложения - вы и вам решать, как правильно писать код. Стоит только учитывать несколько факторов:
- сможет ли его без труда прочитать кто-то другой, если вы его покажете
- сможете ли вы его сами прочитать спустя какое-то время (например через полгода)
- сможете ли вы его потом дорабатывать не переписывая весь код сначала
Есть плотно связанные с этим принципы ООП SOLID, до них я вам тоже советую со временем добраться.
По заявкам телезрителей, вот пример с кортежем
(int, int, int) GetNumbers()
{
return (1, 2, 3);
}
(int a, int b, int c) = GetNumbers();
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Самая главная проблема функций, написанных без явной передачи параметров и имеющих "побочные эффекты" - это то, что их непонятно как тестировать. Функции, которые получают все параметры на вход в явном виде и возвращают результат в явном виде, легко тестировать и можно быть уверенным (до какой-то степени), что они работают так, как задумано, если они покрыты тестами.
Поэтому да, в идеале такой код нужно переписывать на нормальный. Вопрос в том, стоит ли пытаться рефакторить имеющийся код или быстрее/лучше будет написать код заново, сразу уже по-правильному.