[Программирование, C++, Проектирование и рефакторинг] Как легко и просто модернизировать код на C++ (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр!
Предлагаем вашему вниманию перевод короткой практичной статьи по борьбе с избыточным legacy в коде на C++. Надеемся, будет интересно.
В последнее время в сообществе C++ активно продвигается использование новых стандартов и модернизация имеющейся базы кода. Однако, еще даже до выхода стандарта C++11 известные эксперты по C++, в частности, Андре Александреску, Скотт Майерс и Герб Саттер пропагандировали обобщенное программирование на C++, которое квалифицировали как «современный дизайн C++». Вот как высказался об этом Андре Александреску:
Современный дизайн C++ определяет и систематически использует обобщенные компоненты – очень гибкие артефакты для проектирования, которые можно смешивать и сочетать для получения насыщенных вариантов поведения в небольшом ортогональном фрагменте кода.
В этом тезисе интересны три утверждения:
- Современный дизайн C++ определяет и систематически использует обобщенные компоненты.
- Очень гибкий дизайн.
- Получение насыщенных вариантов поведения при помощи небольшого, ортогонального фрагмента кода.
Модернизация кода, написанного на C++, не ограничивается внедрением новых стандартов, но и предполагает использование наилучших практик, применимых в любом языке программирования и помогающих улучшить базу кода. Для начала давайте обсудим некоторые простые шаги, позволяющие вручную модернизировать базу кода. В третьем разделе поговорим об автоматической модернизации кода.
Вручную модернизируем исходный код
Возьмем для примера алгоритм и попробуем его модернизировать. Алгоритмы применяются для расчетов, обработки данных и автоматического получения выводов. Программирование алгоритма – порой нетривиальная задача и зависит от его сложности. В C++ прилагаются значительные усилия для упрощения реализации и повышения мощности алгоритмов.
Давайте попробуем модернизировать эту реализацию алгоритма быстрой сортировки:
// Функция разделения
int partition(int* input,int p,int r){
int pivot = input[r];
while( p < r ){
while( input[p]< pivot )
p++;
while( input[r]> pivot )
r--;
if( input[p]== input[r])
p++;
elseif( p < r ){
int tmp = input[p];
input[p]= input[r];
input[r]= tmp;
}
}
return r;
}
// Рекурсивная функция быстрой сортировки
void quicksort(int* input,int p,int r){
if( p < r ){
int j = partition(input, p, r);
quicksort(input, p, j-1);
quicksort(input, j+1, r);
}
}
В конце концов, у всех алгоритмов есть определенные общие черты:
- Использование контейнера для элементов определенного вида и их перебор.
- Сравнение элементов
- Некоторые операции над элементами
В нашей реализации контейнером является необработанный массив целых чисел, и мы перебираем операции увеличения и уменьшения на единицу. Сравнение выполняется при помощи “<” и “>”, а также мы совершаем над данными некоторые операции, например, меняем их местами.
Давайте попробуем улучшить каждый из этих признаков алгоритма:
Шаг 1: Меняем контейнеры на итераторы
Если мы откажемся от обобщенных контейнеров, то будем вынуждены пользоваться лишь элементами определенного типа. Для применения того же алгоритма к другим типам нам придется копировать и вставлять код. Обобщенные контейнеры решают эту проблему и позволяют использовать элемент любого вида. Например, в алгоритме быстрой сортировки можно применить std::vector<T> в качестве контейнера вместо необработанного массива.
Необработанный массив или std::vector – это всего лишь одна возможность из разнообразных вариантов, позволяющих представить множество элементов. Тот же алгоритм применим и к связному списку, и к очереди, и к любому другому контейнеру. При работе с итератором лучше всего абстрагировать используемый контейнер.
Итератор – это любой объект, который, указывая на элемент в некотором диапазоне, может перебрать все элементы данного диапазона при помощи набора операторов (в который входят, как минимум, оператор увеличения на единицу (++) и оператор разыменования (*)). Итераторы подразделяются на пять категорий в зависимости от реализуемой ими функции: Ввод, Вывод, Однонаправленный итератор, Двунаправленный итератор и случайный доступ.
В нашем алгоритме мы должны указать, какой итератор будем использовать. Для этого нам нужно выявить, какие итерации у нас используются. В алгоритме быстрой сортировки применяются итерации увеличения на единицу и уменьшения на единицу. Следовательно, нам нужен двунаправленный итератор. При помощи итераторов можно определить метод вот так:
template< typename BidirectionalIterator >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last )
Шаг 2: Делаем компаратор обобщенным, если это возможно
В некоторых алгоритмах приходится обрабатывать не только числа, но и, например, строку или класс. В таком случае нужно сделать компаратор обобщенным; это позволит нам добиться большей обобщенности всего алгоритма.
Алгоритм быстрой сортировки вполне можно применить и к списку строк. Соответственно, нам лучше подойдет обобщенный компаратор.
Воспользовавшись обобщенным компаратором, можно модифицировать определение, вот так:
template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp )
Этап 3: Заменяем имеющиеся операции стандартными
В большинстве алгоритмов используются повторяющиеся операции, например, min, max и swap. При выполнении таких операций лучше не изобретать велосипед и использовать стандартную реализацию, существующую в заголовке <algorithm>.
В нашем случае можно использовать метод swap из стандартной библиотеки STL, а не создавать наш собственный метод.
std::iter_swap( pivot, left );
А вот измененный результат, полученный после трех этих шагов:
#include <functional>
#include <algorithm>
#include <iterator>
template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) {
if( first != last ) {
BidirectionalIterator left = first;
BidirectionalIterator right = last;
BidirectionalIterator pivot = left++;
while( left != right ) {
if( cmp( *left, *pivot ) ) {
++left;
} else {
while( (left != right) && cmp( *pivot, *right ) )
--right;
std::iter_swap( left, right );
}
}
--left;
std::iter_swap( pivot, left );
quick_sort( first, left, cmp );
quick_sort( right, last, cmp );
}
}
template< typename BidirectionalIterator >
inline void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) {
quick_sort( first, last,
std::less_equal< typename std::iterator_traits< BidirectionalIterator >::value_type >()
);
}
У данной реализации есть следующие достоинства:
- Применима к элементам разного рода.
- Контейнер может представлять собой вектор, множество, список или любой иной, снабженный двунаправленным итератором.
- Данная реализация использует оптимизированные и протестированные стандартные функции.
Автоматическая модернизация
Интересно автоматически выявлять места, где можно использовать определенные возможности C++11/C++14/C++17 и, если условия благоприятствуют, автоматически менять код. Для таких целей существует полнофункциональный инструмент clang-tidy, используемый для автоматического преобразования кода C++, написанного в соответствии со старыми стандартами. После такого преобразования в коде, там, где это уместно, используются возможности из более новых стандартов.
Вот некоторые участки, на которых clang-tidy предлагает модернизировать код:
- Переопределение: найдите места, где можно добавить указатель переопределения для функции экземпляра, переопределяющей виртуальную функцию в базовом классе, при этом еще не имеющей такого указателя
- Преобразование циклов: найдите циклы вида for(…; …; …), чтобы заменить их новыми циклами на основе диапазона, в которых можно указать начало и конец области для перебора, а далее пользоваться новым выражением для этой цели.
- Передача по значению: найдите параметры const-ref, которым пошла бы на пользу идиома передачи по значению.
- auto_ptr: находите в коде выходящие из употребления std::auto_ptr и заменяйте их std::unique_ptr.
- Авто-указатель: находите места, где можно использовать указатель типа auto в объявлениях переменных.
- nullptr: находите нулевые литералы, чтобы заменять их nullptr там, где это уместно.
- std::bind: такая проверка позволяет найти случаи использования std::bind и заменить простые случаи такого рода лямбдами, там, где это уместно. Там, где требуется, лямбды будут использовать захват значения.
- Устаревшие заголовки: Некоторые заголовки из C были выведены из употребления в C++ и больше не приветствуются в базах кода на этом языке. Некоторые не действуют в C++. Подробнее об этом рассказано в соответствующем разделе стандарта C++ 14 [depr.c.headers].
- std::shared_ptr: такая проверка позволяет выявить случаи создания объектов std::shared_ptr путем явного вызова конструктора и с выражением new, после чего заменяет их вызовом std::make_shared.
- std::unique_ptr: такая проверка позволяет выявить случаи создания объектов std::shared_ptr путем явного вызова конструктора и с выражением new, после чего заменяет их вызовом std::make_unique, появившимся в C++14.
- Литералы неформатированной строки: такая проверка выборочно заменяет строковые литералы, содержащие экранированные символы, на литералы неформатированной строки.
Разработчики, освоившие Clang, легко научатся обращаться и с инструментом clang-tidy. Но при работе с Visual C++, а также с другими компиляторами можно использовать CppDepend, в состав которого входит clang-tidy.
===========
Источник:
habr.com
===========
===========
Автор оригинала: CppDepend Team
===========Похожие новости:
- [Программирование, Компиляторы, C, Разработка под Windows] Использование SEH в 32 разрядных приложениях Windows с компилятором Mingw-W64
- [Программирование, ReactJS] Создание React-компонентов с помощью Hygen (перевод)
- [Программирование, Разработка под iOS, Разработка мобильных приложений, Swift] Приложение на SwiftUI в AppStore – сложности разработки
- [Разработка веб-сайтов, Программирование, Исследования и прогнозы в IT, Облачные сервисы] Самые популярные языки программирования для бэкенда: для чего они подходят лучше всего и какие компании их используют (перевод)
- [Реверс-инжиниринг, Программирование микроконтроллеров, Прототипирование, Интернет вещей, DIY или Сделай сам] Подключаемся к станку по изготовлению профлиста и считываем из него прокатную длинну
- [Тестирование IT-систем, Программирование, Java, IT-стандарты, Промышленное программирование] Принцип слоеного теста
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS] Вопросы для собеседования по хукам React (перевод)
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Как мы сделали миграцию пользовательских данных с нативного приложения на Flutter
- [Разработка веб-сайтов, Программирование, Dart, Flutter] DartUP 2020: итоги и видеозаписи докладов
- [Swift, Профессиональная литература] Книга «Swift. Основы разработки приложений под iOS, iPadOS и macOS. 6-е изд. дополненное и переработанное»
Теги для поиска: #_programmirovanie (Программирование), #_c++, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_c++, #_clang, #_refaktoring (рефакторинг), #_legacykod (legacy-код), #_programmirovanie (программирование), [url=https://torrents-local.xyz/search.php?nm=%23_blog_kompanii_izdatelskij_dom_«piter»&to=0&allw=0&o=1&s=0&f%5B%5D=820&f%5B%5D=959&f%5B%5D=958&f%5B%5D=872&f%5B%5D=967&f%5B%5D=954&f%5B%5D=885&f%5B%5D=882&f%5B%5D=863&f%5B%5D=881&f%5B%5D=860&f%5B%5D=884&f%5B%5D=865&f%5B%5D=873&f%5B%5D=861&f%5B%5D=864&f%5B%5D=883&f%5B%5D=957&f%5B%5D=859&f%5B%5D=966&f%5B%5D=956&f%5B%5D=955]#_blog_kompanii_izdatelskij_dom_«piter» (
Блог компании Издательский дом «Питер»
)[/url], #_programmirovanie (
Программирование
), #_c++, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:03
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! Предлагаем вашему вниманию перевод короткой практичной статьи по борьбе с избыточным legacy в коде на C++. Надеемся, будет интересно. В последнее время в сообществе C++ активно продвигается использование новых стандартов и модернизация имеющейся базы кода. Однако, еще даже до выхода стандарта C++11 известные эксперты по C++, в частности, Андре Александреску, Скотт Майерс и Герб Саттер пропагандировали обобщенное программирование на C++, которое квалифицировали как «современный дизайн C++». Вот как высказался об этом Андре Александреску: Современный дизайн C++ определяет и систематически использует обобщенные компоненты – очень гибкие артефакты для проектирования, которые можно смешивать и сочетать для получения насыщенных вариантов поведения в небольшом ортогональном фрагменте кода.
В этом тезисе интересны три утверждения:
Модернизация кода, написанного на C++, не ограничивается внедрением новых стандартов, но и предполагает использование наилучших практик, применимых в любом языке программирования и помогающих улучшить базу кода. Для начала давайте обсудим некоторые простые шаги, позволяющие вручную модернизировать базу кода. В третьем разделе поговорим об автоматической модернизации кода. Вручную модернизируем исходный код Возьмем для примера алгоритм и попробуем его модернизировать. Алгоритмы применяются для расчетов, обработки данных и автоматического получения выводов. Программирование алгоритма – порой нетривиальная задача и зависит от его сложности. В C++ прилагаются значительные усилия для упрощения реализации и повышения мощности алгоритмов. Давайте попробуем модернизировать эту реализацию алгоритма быстрой сортировки: // Функция разделения
int partition(int* input,int p,int r){ int pivot = input[r]; while( p < r ){ while( input[p]< pivot ) p++; while( input[r]> pivot ) r--; if( input[p]== input[r]) p++; elseif( p < r ){ int tmp = input[p]; input[p]= input[r]; input[r]= tmp; } } return r; } // Рекурсивная функция быстрой сортировки void quicksort(int* input,int p,int r){ if( p < r ){ int j = partition(input, p, r); quicksort(input, p, j-1); quicksort(input, j+1, r); } } В конце концов, у всех алгоритмов есть определенные общие черты:
В нашей реализации контейнером является необработанный массив целых чисел, и мы перебираем операции увеличения и уменьшения на единицу. Сравнение выполняется при помощи “<” и “>”, а также мы совершаем над данными некоторые операции, например, меняем их местами. Давайте попробуем улучшить каждый из этих признаков алгоритма: Шаг 1: Меняем контейнеры на итераторы Если мы откажемся от обобщенных контейнеров, то будем вынуждены пользоваться лишь элементами определенного типа. Для применения того же алгоритма к другим типам нам придется копировать и вставлять код. Обобщенные контейнеры решают эту проблему и позволяют использовать элемент любого вида. Например, в алгоритме быстрой сортировки можно применить std::vector<T> в качестве контейнера вместо необработанного массива. Необработанный массив или std::vector – это всего лишь одна возможность из разнообразных вариантов, позволяющих представить множество элементов. Тот же алгоритм применим и к связному списку, и к очереди, и к любому другому контейнеру. При работе с итератором лучше всего абстрагировать используемый контейнер. Итератор – это любой объект, который, указывая на элемент в некотором диапазоне, может перебрать все элементы данного диапазона при помощи набора операторов (в который входят, как минимум, оператор увеличения на единицу (++) и оператор разыменования (*)). Итераторы подразделяются на пять категорий в зависимости от реализуемой ими функции: Ввод, Вывод, Однонаправленный итератор, Двунаправленный итератор и случайный доступ. В нашем алгоритме мы должны указать, какой итератор будем использовать. Для этого нам нужно выявить, какие итерации у нас используются. В алгоритме быстрой сортировки применяются итерации увеличения на единицу и уменьшения на единицу. Следовательно, нам нужен двунаправленный итератор. При помощи итераторов можно определить метод вот так: template< typename BidirectionalIterator >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) Шаг 2: Делаем компаратор обобщенным, если это возможно В некоторых алгоритмах приходится обрабатывать не только числа, но и, например, строку или класс. В таком случае нужно сделать компаратор обобщенным; это позволит нам добиться большей обобщенности всего алгоритма. Алгоритм быстрой сортировки вполне можно применить и к списку строк. Соответственно, нам лучше подойдет обобщенный компаратор. Воспользовавшись обобщенным компаратором, можно модифицировать определение, вот так: template< typename BidirectionalIterator, typename Compare >
void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) Этап 3: Заменяем имеющиеся операции стандартными В большинстве алгоритмов используются повторяющиеся операции, например, min, max и swap. При выполнении таких операций лучше не изобретать велосипед и использовать стандартную реализацию, существующую в заголовке <algorithm>. В нашем случае можно использовать метод swap из стандартной библиотеки STL, а не создавать наш собственный метод. std::iter_swap( pivot, left );
А вот измененный результат, полученный после трех этих шагов: #include <functional>
#include <algorithm> #include <iterator> template< typename BidirectionalIterator, typename Compare > void quick_sort( BidirectionalIterator first, BidirectionalIterator last, Compare cmp ) { if( first != last ) { BidirectionalIterator left = first; BidirectionalIterator right = last; BidirectionalIterator pivot = left++; while( left != right ) { if( cmp( *left, *pivot ) ) { ++left; } else { while( (left != right) && cmp( *pivot, *right ) ) --right; std::iter_swap( left, right ); } } --left; std::iter_swap( pivot, left ); quick_sort( first, left, cmp ); quick_sort( right, last, cmp ); } } template< typename BidirectionalIterator > inline void quick_sort( BidirectionalIterator first, BidirectionalIterator last ) { quick_sort( first, last, std::less_equal< typename std::iterator_traits< BidirectionalIterator >::value_type >() ); } У данной реализации есть следующие достоинства:
Автоматическая модернизация Интересно автоматически выявлять места, где можно использовать определенные возможности C++11/C++14/C++17 и, если условия благоприятствуют, автоматически менять код. Для таких целей существует полнофункциональный инструмент clang-tidy, используемый для автоматического преобразования кода C++, написанного в соответствии со старыми стандартами. После такого преобразования в коде, там, где это уместно, используются возможности из более новых стандартов. Вот некоторые участки, на которых clang-tidy предлагает модернизировать код:
Разработчики, освоившие Clang, легко научатся обращаться и с инструментом clang-tidy. Но при работе с Visual C++, а также с другими компиляторами можно использовать CppDepend, в состав которого входит clang-tidy. =========== Источник: habr.com =========== =========== Автор оригинала: CppDepend Team ===========Похожие новости:
Блог компании Издательский дом «Питер» )[/url], #_programmirovanie ( Программирование ), #_c++, #_proektirovanie_i_refaktoring ( Проектирование и рефакторинг ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 16:03
Часовой пояс: UTC + 5