Шаблонный код, boilerplate-код (англ. boilerplate code) — нетворческий программный код, который программисту приходится писать вследствие требований языка программирования, операционной системы, библиотеки подпрограмм, манеры программирования и прочего. Название «шаблонный» говорит, что он повторяется из функции в функцию, из программы в программу с минимальными изменениями. Шаблонными, в числе прочего, будут:
Например, в простейшем «Hello, world» на Си все строки, кроме собственно printf(...)
, будут шаблонными.
#include <stdio.h> //< шаблонный — подключение модуля
int main() //< шаблонный — точка входа
{ //< шаблонный — точка входа
printf("Hello, world!\n"); //< творческий!!
return 0; //< шаблонный — выход
} //< шаблонный — точка входа
К шаблонному коду близок так называемый bookkeeping code — творческий, но сравнительно простой код, обеспечивающий дополнительные стороны функционирования программы наподобие загрузки-сохранения.
Слово «boilerplate» по-английски означает «котельное железо». Применительно к шаблонному тексту встречается уже в 1950-х. Этимология неясна и определённо происходит из лексикона полиграфистов, основные версии:
Стандартный код на Java:
public class Book {
private String m_ISBN;
private String m_Title;
private String m_SubTitle;
private String m_Autor;
public String getISBN() { return m_ISBN; }
public void setISBN(String pISBN) { m_ISBN = pISBN; }
public String getTitle() { return m_Title; }
public void setTitle(String pTitle) { m_Title = pTitle; }
public String getSubTitle() { return m_SubTitle; }
public void setSubTitle(String pSubTitle) { m_SubTitle = pSubTitle; }
public String getAutor() { return m_Autor; }
public void setAutor(String pAutor) { m_Autor = pAutor; }
}
Аналогичный код на C#…
public class Book
{
public string ISBN { get; set; }
public string Title { get; set; }
public string SubTitle { get; set; }
public string Autor { get; set; }
}
…и на Kotlin
data class Book(var ISBN: String, var Title: String, var SubTitle: String, var Autor: String)
Стоит сказать, что языки программирования стараются избавляться от шаблонного кода. Примером может быть 17 версия Java, в которой появился специальный тип record, позволяющий писать меньше кода, подобно Kotlin
public record Book(String m_ISBN, String m_Title, String m_SubTitle, String m_Autor) {
}
Например, в системе Qt Widgets код минимальной формы с кнопкой состоит из нескольких файлов, приведём один.
//== FmMain.cpp ==
#include "FmMain.h"
#include "ui_FmMain.h"
FmMain::FmMain(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::FmMain)
{
ui->setupUi(this);
}
FmMain::~FmMain()
{
delete ui;
}
void FmMain::on_btDemo_clicked()
{
ui->btDemo->setText("Demo!!"); //< Только эту строку мы пишем вручную!
}
При этом только одну строку программист пишет вручную.
Пример 1. В Qt+MinGW для создания автономной консольной программы под Windows надо сделать:
win32-g++ {
QMAKE_CXXFLAGS += -static-libgcc -static-libstdc++
LIBS += -static -lpthread
}
Это также считается шаблонным кодом. Поскольку мелкие учебные и экспериментальные программы обычно являются автономными и консольными, возможно, следовало бы добавлять подобный код всегда при создании пустой консольной программы — а разработчик более сложного ПО, использующего чужие динамические библиотеки, просто стирал бы его.
Пример 2. Полный HTML выглядит так:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Test</title>
</head>
<body>
<p>Hello world!</p>
</body>
</html>
Стандарт WHATWG HTML позволяет опустить html, head и body[6]. Meta charset можно опустить, если HTML пришёл от сервера и в заголовках HTTP есть кодировка. Так что остаётся…
<!DOCTYPE html>
<title>Test</title>
<p>Hello world!</p>
Пример 1. В том же Qt есть две программы, создающие часть Си++-кода формы — метаобъектный компилятор (moc) и компилятор интерфейса (uic).
Пример 2. Создание копии объекта с учётом умных указателей Си++11 выглядит так.
#include <iostream>
#include <memory>
class Abstract {
public:
auto clone() const { return std::unique_ptr<Abstract>{ vclone() }; }
protected:
virtual Abstract* vclone() const = 0;
};
class Abstract2 : public Abstract {
public:
auto clone() const { return std::unique_ptr<Abstract2>{ vclone() }; }
protected:
virtual Abstract2* vclone() const = 0;
};
class Concrete : public Abstract2 {
public:
auto clone() const { return std::unique_ptr<Concrete>{ vclone() }; }
protected:
Concrete* vclone() const override { return new Concrete(*this); }
};
int main()
{
Concrete impl;
Abstract* a = &impl;
std::unique_ptr<Abstract> b = a->clone();
std::unique_ptr<Concrete> c = impl.clone();
return 0;
}
Здесь шаблонный код — реализации функций vclone
и clone
.
С препроцессором Си этот код будет выглядеть так, и чем больше классов в иерархии, тем больше выигрыш:
#define IMPL_CLONE_1(Class, Body) \
public: auto clone() const { return std::unique_ptr<Class>{ vclone() }; } \
protected: virtual Class* vclone() const Body
#define IMPL_CLONE_ABSTRACT(Class) IMPL_CLONE_1(Class, override = 0;)
#define IMPL_CLONE_CONCRETE(Class) IMPL_CLONE_1(Class, override { return new Class(*this); })
class Abstract {
IMPL_CLONE_1(Abstract, =0;)
};
class Abstract2 : public Abstract {
IMPL_CLONE_ABSTRACT(Abstract2)
};
class Concrete : public Abstract2 {
IMPL_CLONE_CONCRETE(Concrete)
};
На Си++ обрезка пробелов (trim) в строках разного типа «на месте» (без выделения памяти) будет выглядеть так:
std::string_view trimSv(std::string_view x) {
// Какая-то реализация
}
std::wstring_view trimSv(std::wstring_view x) {
// Похожая реализация
}
И то же с метапрограммированием:
namespace detail {
template <class Ch, class Traits>
std::basic_string_view<Ch, Traits> trimSv(std::basic_string_view<Ch, Traits> x) {
// Реализация
}
}
template <class S>
inline auto trimSv(const S& x)
{ return detail::trimSv(static_cast<std::basic_string_view>(x)); }
Код также многословный (вторая функция нужна, чтобы под шаблон подходил не только std::string_view, но и близкие к нему типы std::string и const char*), но как минимум без повторов.