Skip to content

Coding Standards

Nikita Kataev edited this page Oct 14, 2024 · 1 revision

Допускается длина строки не более 80 символов. Исключение делается только для строковых констант, не умещающихся в данный предел. Использование символа табуляции в коде не допускается, данные символы должны быть заменены на пробелы (автоматическая замена поддерживается в большинстве средств разработки). Стандартный размер отступа - 2 символа, все завершающие пробельные символы должны удаляться.

Документирование кода

Все комментарии, размещенные в коде, должны быть написаны на английском языке. Допускается два вида комментариев:

// Обычный комментарий

/// Документирующий комментарий оформляется в формате
/// Doxygen-комментария (http://www.doxygen.org/).
///
/// Документирующие Doxygen-комментарии должны быть написаны для всех открытых
/// методов, членов, типов класса, всех доступных извне элементов (в том числе
/// классов, пространств имен, типов).
/// \note Рекомендуется указывать комментарии и для остальных объектов.

В начале каждого файла должен содержаться блок комментариев кратко описывающий содержимое файла, а также информация о лицензионном соглашении (NOTICE).

//===--- PrivateAnalysis.h - Private Variable Analyzer ----------*- C++ -*-===//
//
//                       Traits Static Analyzer (SAPFOR)
//
//
// Copyright 2018 DVM System Group
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//===----------------------------------------------------------------------===//
//
// This file defines passes to determine locations which can be privatized.
// We use data-flow framework to implement this kind of analysis. This file
// contains elements which is necessary to determine this framework.
// The following articles can be helpful to understand it:
//  * "Automatic Array Privatization" Peng Tu and David Padua
//  * "Array Privatization for Parallel Execution of Loops" Zhiyuan Li.
//
//===----------------------------------------------------------------------===//

Отступы

Перед объектами, расположенными в пространстве имен отступы не ставятся:

namespace tsar {
/// Color of a node which depth-first search (DFS) has visited.
enum DFSColor {
  ...

После заголовка функции, ключевого слова template, специализируемого класса или функции отступы не ставятся.

/// Return true if the specified directed graph is acyclic.
///
/// \param [in] G Directed graph, it can not be null.
/// \pre The llvm::GraphTraits class should be specialized by GraphType.
/// Note that GraphType is generally a pointer type, for example BasicBlock *.
template<class GraphType> inline bool isDAG(GraphType G) {
  return !findBackEdge<GraphType>(G).first;
}

Отступы ставятся после ключевых слов if, for, while и аналогичных.

Если параметры функции не умещаются в границы 80 символов, то перед ними ставится отступ 4 символа.

  template<class location_iterator, class ResultSet>
  static void difference(
      const location_iterator &LocBegin, const location_iterator &LocEnd,
      const LocationSet &LocSet, ResultSet &Result) {
    if (LocSet.mLocations.empty())
      ...

Открывающая фигурная скобка размещается на той же строке что и предшествующая ей конструкция. Ключевое слово else размещается на той же строке, что и предшествующая закрывающая фигурная скобка. Если в блоке if-then-else хотя бы один раз встречаются фигурные скобки, то фигурные скобки должны стоят во всех ветках. *Строки идут подряд без пропусков за исключением отступов перед введением новых классов, типов, функций, блока конструкций using и #include.

  if (SToNode == Blocks.end()) {
    BBToN.second->addSuccessor(ExitNode);
    ExitNode->addPredecessor(BBToN.second);
  } else if (*SI == LT::getHeader(L)) {
    ...
  } else if (SToNode->second != BBToN.second) {
    BBToN.second->addSuccessor(SToNode->second);
  }

Заголовочные файлы

Подключаемые заголовочные файлы должны быть перечислены в лексикографическом порядке. При этом сначала указываются файлы относящиеся непосредственно к изменяемому проекту, данные файлы экранируются ". Имена остальных файлов экранируются <...>, сначала указываются файлы относящиеся к BCL, затем к Clang, LLVM и самыми последними указываются файлы стандартной библиотеки язка С++.

Если заголовочные файлы доволяются в файл .cpp то независимо от имени указывается заголовончый файл логически связанный с данным .cpp файлом (чаще всего это файл с тем же именем, но расширением .h).

Пример заголовочных файлов, подключаемых в файле tsar/lib/Support/Clang/Utils.cpp:

#include "tsar/Support/Clang/Utils.h"
#include "tsar/Support/Utils.h"
#include <bcl/utility.h>
#include <clang/Analysis/CFG.h>
#include <clang/ASTMatchers/ASTMatchFinder.h>
#include <clang/Basic/LangOptions.h>
#include <clang/Format/Format.h>
#include <clang/Frontend/ASTUnit.h>
#include <clang/Lex/Lexer.h>
#include <clang/Tooling/Tooling.h>
#include <llvm/ADT/DenseSet.h>
#include <llvm/ADT/SmallPtrSet.h>
#include <llvm/Support/Debug.h>
#include <llvm/Support/raw_ostream.h>
#include <numeric>
#include <regex>

Для защиты от повторного подключения файлов внутри заголовочных файлов используются макросы (вместо директивы #pragma once):

#ifndef TSAR_CLANG_UTILS_H
#define TSAR_CLANG_UTILS_H
...
#endif//TSAR_CLANG_UTILS_H

Имена

Имена классов и типов должны начинаться с заглавной буквы. Имена функций начинаются со строчной буквы. Использование нижнего подчеркивания _ и строчных букв в именах классов и типов допускается только для stl-подобных конструкций.

  ...
  /// This type used to iterate over all nodes in the region body.
  using node_iterator = std::vector<DFNode *>::const_iterator;

  /// This type used to iterate over internal regions.
  using region_iterator = std::vector<DFRegion *>::const_iterator;
  ...
  /// Return iterator that points to the beginning of the nodes list.
  node_iterator node_begin() const { return mNodes.begin(); }

  /// Return iterator that points to the ending of the nodes list.
  node_iterator node_end() const { return mNodes.end(); }
  ...

Имена переменных должны начинаться с заглавной буквы. Для закрытых членов класса должен ставиться префикс m, для глобальных переменных (использование которых не рекомедуется) - префикс g.

Имена должны быть понятны и точно описывать назначение именуемой сущности. Допускаются общеупотребительные в рамках проекта сокращения имен (BasicBlock - BB, Function - F, Control Flow Graph - CFG и т.д.), или сокращения имен с небольшой областью видимости для более компактного размещения кода.

  ...
  for (auto I = LT::block_begin(L), E = LT::block_end(L); I != E; ++I) {
    if (Blocks.count(*I))
      continue;
    auto *N = new DFBlock(*I);
    R->addNode(N);
    Blocks.insert(std::make_pair(*I, N));
  }
  ...

Порядок объявления методов и членов классов

Закрытые методы и члены классов должны быть размещены в конце описания класса. Объявление класса должно начинаться с секции public, затем следует секция protected и секция private.

  ...
  /// Specifies that there are unknown instructions in the node.
  ///
  /// \return False if it has been already specified.
  bool addUnknownInst(llvm::Instruction *I) {
    assert(I && "Instruction must not be null!");
    return mUnknownInsts.insert(I).second;
  }

private:
  LocationSet mDefs;
  LocationSet mMayDefs;
  LocationSet mUses;
  llvm::AliasSetTracker mExplicitAccesses;
  PointerSet mAddressAccesses;
  InstructionSet mUnknownInsts;
};

Использование открытых членов класса допускается только в структурах, если отсутствуют другие закрытые члены класса и только если структура представляет из себя простой контейнер для хранения данных, не реализующий дополнительной функциональности.

struct GlobalOptions {
  /// Print only names of files instead of full paths.
  bool PrintFilenameOnly = false;
  /// Disallow unsafe integer type cast in analysis passes.
  bool IsSafeTypeCast = true;
  /// Assume that subscript expression is in bounds value of an array dimension.
  bool InBoundsSubscripts = false;
  ...
};

Классы не допускающие копирования

Классы не допускающие копирования должны закрыто наследовать класс bcl::Uncopyable определенный в файле bcl/utility.h

namespace llvm {
/// This pass determines locations which can be privatized.
class PrivateRecognitionPass :
    public FunctionPass, private bcl::Uncopyable {
  ...

Функции

Для функций должен соблюдаться следующий порядок задания параметров: сначала указываются входные, затем выходные. Не следует добавлять новые параметры функции в конец ее объявления только потому, что они были добавлены последними.

Предпочитайте писать компактные функции. Если функция занимает слишком много строк кода, стоит подумать о том как ее разбить на несколько компактных. Это поможет сделать код более понятным, но не должно нарушать логическую структуру программы. Сопровождать и отлаживать большие функции непросто, особенно когда этим занимаются другие разработчики.

Перечисления

Перечисления, используемые для задания вида объекта, должны начинаться со вспомогательного элемента с префиксом FIRST и заканчиваться тремя вспомогательными элементами с префиксами LAST, INVALID, NUMBER соответственно.

  /// Kind of a node.
  /// If you add a new kind of region it should be in the range between
  /// FIRST_KIND_REGION and LAST_KIND_REGION
  enum Kind {
    FIRST_KIND = 0,
    KIND_BLOCK = FIRST_KIND,
    KIND_ENTRY,
    KIND_EXIT,
    KIND_LATCH,

    FIRST_KIND_REGION,
    KIND_LOOP = FIRST_KIND_REGION,
    KIND_FUNCTION,
    LAST_KIND_REGION = KIND_FUNCTION,

    LAST_KIND = LAST_KIND_REGION,
    INVALID_KIND,
    NUMBER_KIND = INVALID_KIND,
  };

В некоторых ситуация допускается использование перечислений без вспомогательных элементов. В этом случае к именам элементов применяются правила именования переменных. Например, следующее перечисление задает битовые флаги. Для удобства используется перегрузка для битовых операций над элементами перечисления, реализованная в LLVM и позволяющая избежать явных приведений типов (LoopBoundKind BoundFlag = LoopStartIsKnown | LoopEndIsKnown).

#include <llvm/ADT/BitmaskEnum.h>

LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE()

  enum LoopBoundKind : short {
    LoopBoundIsUnknown = 0,
    LoopStartIsKnown = 1u << 0,
    LoopEndIsKnown = 1u << 1,
    LoopStepIsKnown = 1u << 2,
    LoopBoundUnsigned = 1u << 3,
    LLVM_MARK_AS_BITMASK_ENUM(LoopBoundUnsigned)
  };

Отладка кода

Для сокращения количества ошибок в коде и времени затраченного на их поиск рекомендуется использовать конструкции assert. Для точного описания проверяемого утверждения следует использовать строковую константу:

  assert(*I == GT::getEntryNode(DFG) &&
    "The first node in the topological order differs from the entry node in the data-flow framework!");

Чтобы пометить область код, которая никода не должны выполняться можно использовать конструкцию LLVM (llvm_unreachable):

  switch (getKind()) {
  default:
    llvm_unreachable("Unknown kind of an alias node!");
    break;
  case KIND_TOP:
    ...

В данном случае аварийный останов программы произойдет, если условие *I == GT::getEntryNode(DFG) окажется ложно. При этом текстовое сообщение, описывающее проверяемое утверждение, не влияет на выполнение проверки и используется в пояснительных целях.

Конструкции assert и llvm_unreachabel могут быть удалены на этапе компиляции и не должны менять поведение программы. После их отключения программа должна оставаться корректной. Следовательно, внутри данных конструкций нельзя изменять переменные программы, используемые не только с целью отладки, вызывать функции, изменяющие состояние программы.

Для проверки утверждений на этапе компиляции могут использоваться конструкции static_assert:

  static_assert(!IsCellExist<CellNext, CellKey>::value,
    "Each cell must be presented only once in the map!");