?: (ровно как и ? x :) на каждом шагу использовать не стоит, слишком там уж много условностей. Лучше уж-таки простым условием, а если свербит, то прописать макрос для препроцессора.
И чем же твоё говно читабельней? Тем, что вместо 100строк я получу овер1к? Отлично - это же так удобно, когда вместо взгляда на полный код - я скролю как мразь.
Эти нулёвые if(str == NULL), это неосиляторство ?: и пацан рождает говняную лапшу. Хорошо хоть додумался спастить функцию у пацанов.
Вообще, возврат некоторой функции вместо реального значения некоторого NULL по смыслу может быть по трем причинам:
1) Функция должна сигнализировать о том, что что-то пошло не так. Тогда она должна возвращать NULL, и результат должен явно проверяться if (result==null)...
2) Функция возвращает NULL, но это нормально, это не сигнал о том, что что-то пошло не так. Тогда функция не должна возвращать NULL, и проверку надо перенести в саму функцию return (result==NULL) ? «unknown» : result
3) Функция возвращает NULL, и в одних случаях это сигнал о том, что что-то пошло не так, а в других случаях это нормально - например в случае печати. Тогда функция должна возвращать NULL и он должен в одних местах обрабатываться явно if (result==NULL), а в других местах, например для печати, мне кажется выгоднее завести функцию const char *printable(const char *) приводящую все возможные результаты исходной функции к печатному виду.
Если выражаться более формально: есть функция f возвращающая результат из домена Ef, и функция g, принимающая параметр из домена Dg. Предположим, Ef не совпадает с Dg, а мы хотим использовать функции так g(f()). Тогда нам нужно написать функцию h, переводящую Ef в Dg, и использовать их так g(h(f())).
Рассмотрим теперь домен X=Ef\Dg. Если в X нет значений, которые мы хотим обрабатывать особым образом (то есть любым образом, кроме передачи в g), то мы можем не определять отдельно f, а определить сразу h*f. Или, если это возможно, сразу определить g*h. Если не возможно ни то, ни другое, то придётся определить отдельно f и g, и в этом случае, мне кажется, намерения будут в коде видны яснее, если явно определить h.
Конечно, иногда для одной f много разных g1...gN, с параметрами из разных доменов. Выгодно или нет определять функции перевода для всех этих доменов, - это решит ситуация.
В рассматриваемом случае f() это get_string(), Ef это множество всех действительных строк и NULL, g(s) это printf(«My string is %s\n», s), Dg это множество всех действительных строк, f(s) это printable(s) сопоставляющая значению NULL действительную строку «unknown», h*f это то же самое, что перенести проверку на NULL в функцию get_string(), g*h это то же самое, что и включить в printf тернарный оператор или выполнить проверку на NULL перед printf. Как бы то ни было, если вы будете держать такой взгляд на происходящее в уме, у вас появится возможность осознанно выбирать между вариантами, а не зацикливаться на одном.
1) Функция должна сигнализировать о том, что что-то пошло не так. Тогда она должна возвращать NULL, и результат должен явно проверяться if (result==null)...
К тому же, многие программисты избегают условной операции (?:) просто потому, что она им
кажется непонятной. Тем не менее, это условное выражение может существенно упростить ваш код и,
следственно, сделать его лучше читаемым. Я думаю, что:
Вы к тому же экономите на двух вызовах printf(). Мне также часто приходится видеть неправильное
использование операций ++ и --. Весь смысл автоинкремента или автодекремента заключается в
соединении этих операций с другими. Вместо:
while ( *p )
{
putchar ( *p );
++p;
}
или:
for ( ; *p ; ++p )
putchar ( *p );
используйте:
while ( *p )
putchar ( *p++ );
Этот код вполне читаем для компетентного программиста на языке С, даже если ему нет
эквивалентной операции в ФОРТРАНе или Паскале.