YM
а теперь кто-то сделал, ну как всегда)
только я хотел вместо колков "шаговики" влепить, чтобы нажал кнопочку и всё подстроилось
Size: a a a
YM
R
R
VC
NK
B
B
VC
M
М
VC

AW
VC
VC
void foo(){
// выделяем память
DataType* data = malloc(sizeof(Data));
// работаем с памятью
process(data);
// освобождаем память
free(data);
}
Пока что все выглядит ок. Но требования меняются, и теперь нам надо обрабатывать данные в зависимости от того что вернула предыдущая обработка.void foo(){
DataType* data = malloc(sizeof(Data));
if(process_1(data)){
if(process_2(data)){
if(process_3(data)){
process_4(data);
}
}
}
free(data);
}
Цикломатическая сложность растет, и если мы продолжим писать в этом же стиле - рано или поздно мы уйдем ifами за правый край экрана (ну или запутаемся в блоках). Код становится неприятно читать и сложно поддерживать, давайте его перепишем на множественный возврат из функции.void foo(){
DataType* data = malloc(sizeof(Data));
if(!process_1(data)){
free(data);
return;
}
if(!process_2(data)){
free(data);
return;
}
if(!process_3(data)){
free(data);
return;
}
process_4(data);
free(data);
}
Это выглядит и читается приемлимо, но поддерживать этот код стало еще сложнее, например очень легко можно забыть освободить память перед возвратом. Для борьбы с этим человечество придумало блоки finally и идиому "Получение ресурса есть инициализация" (RAII). К сожалению мы любим простреленные ноги и пишем на языке Си, в котором подобное невозможно реализовать не превратив код обмазанный макросами в некрономикоподобный диалект. Что же мы можем сделать?void foo(){
DataType* data = malloc(sizeof(Data));
if(!process_1(data)) goto finally;
if(!process_2(data)) goto finally;
if(!process_3(data)) goto finally;
process_4(data);
finally:
free(data);
}
К сожалению столь лаконичный код получился с использованием goto. Использование оператора goto в целом критикуется, так как программу с его использованием читать на порядки сложнее, как минимум неявны начала и концы переходов, для подробной критики см "Go To Statement Considered Harmful" Эдсгера Дейкстры. Нам нужен блок который бы исполнялся один раз и из которого мы можем выйти оператором отличным от goto, например возьмем отдельную функцию.void process_data(DataType* data){
assert(data);
if(!process_1(data)) return;
if(!process_2(data)) return;
if(!process_3(data)) return;
process_4(data);
}
void foo(){
DataType* data = malloc(sizeof(Data));
process_data(data);
free(data);
}
Приемлимый и поддерживаемый вариант, но требующий передачи ресурса в аргумент (а следовательно проверки аргумента) и требующий от программиста скакать по функциям, плюс загрязняющий область видимости.void foo(){
DataType* data = malloc(sizeof(Data));
do {
if(!process_1(data)) break;
if(!process_2(data)) break;
if(!process_3(data)) break;
process_4(data);
} while(false);
free(data);
}
В результате мы получили идиому которая явно указывает начало-конец перехода, выделяет блоком код который можно менять не отвлекаясь на освобождение ресурса, и в целом код который довольно лаконично выглядит.D
VC
D
VC