Плохая ли практика изменять входных параметров методом?
Некоторые ЯП позволяют менять объекты, переданные в методы в качестве аргументов. Но это может вызывать очень много сайд эффектов, которые другой программист, работающий с кодом просто не ожидает.
Вот пример на JS
let hierarchy = []; // Массив объектов
let hierarchyStack = []; // Массив объектов
let endConnection = function(currentConnection) {
addConnToParent(currentConnection);
addToHierarchy(currentConnection);
addPointsToConnection (currentConnection);
};
let addToHierarchy = function(currentConnection) {
currentConnection = {'foo': 'bar'};
hierarchy.push(currentConnection);
};
let addConnectionToParent = function(currentConnection) {
let parentConn = hierarchyStack[hierarchyStack.length - 2];
// ... some code
parentConn.children.push(currentConnection); // children - массив объектов
};
let addPointsToConnection = function(connection) {
// ... some code
connection.points = schemaData.slice(2, 4); // points - массив объектов
};
В каждом из методов изменяется переданный объект. В итоге в исходном методе после трёх методов исходный массив вообще может быть не похож на себя и изуродован до неузнаваемости.
А вот Java
class Test {
public int hello = 111;
}
//....
public static void main (String[] args) throws java.lang.Exception {
Test a = new Test();
System.out.println(a.hello);
changeObjectValue(a);
System.out.println(a.hello);
}
public static void changeObjectValue(Test b) {
changeObjectValue2(b);
}
public static void changeObjectValue2(Test c) {
changeObjectValue3(c);
}
public static void changeObjectValue3(Test d) {
d.hello= 888;
}
Итог: через все эти методы этот объект "всплыл" изменённым. А таких методов может быть не один, в разных классах.
Если этот объект не используется в методах типа стримов - корректно ли такое использование и написание методов? Когда корректно возвращать изменённый аргумент, а не создавать сайд эффекты?
И в каких случаях можно изменить аргумент и это будет удобно?
Ответы (1 шт):
Нет, это не плохая практика сама по себе. Вопрос в том, что вы и другие пользователи должны очевидным образом (лучше всего — из названия) понимать, что именно изменяет функция.
Если функция, к примеру, имеет сигнатуру
void Setup(Connection connection)
то очевидно, что ничего кроме побочного эффекта такая функция делать не может, что она настраивает соединение, и просто обязана изменять свой аргумент.
Если же функция выглядит как чистая функция, получает информацию лишь из аргументов, и возвращает результат лишь в виде возвращаемого значения, то и вести она должна соответствующим образом. Например, вот такая функция сложения:
Point operator+ (Point& other)
{
this.X += other.X;
this.Y += other.Y;
return *this;
}
(знакомо, да?) совершенно никуда не годится.
Я бы предложил не смешивать концепции, и стараться делать функции такими:
- либо ваша функция производит лишь побочный эффект, тогда она типа void (или в крайнем случае bool, чтобы сигнализировать об успехе или неудаче, если вам не нравится идея с исключением),
- либо ваша функция чистая или почти чистая, вычисляет результат на основе входящих аргументов, и возвращает лишь возвращаемое значение.
Впрочем, это не догма: например, внешне чистая функция может внутри устроить кеширование. Главное, чтобы смешанная семантика не была публичным контрактом функции.