Skip to content

Development Standards

Mahan Khalili edited this page Dec 3, 2025 · 5 revisions

📚 Development Standards

This document is prepared to define development standards in the Text Forge project. Its goal is to create cohesion, maintain code quality, and facilitate collaboration between developers. Adhering to these standards ensures that the project code remains stable, readable, and maintainable. This guide includes coding style principles, best practices, and procedures for participating in development.

Quick Navigation


Goals & Philosophy

The goal of this document is to establish a common development language among developers, maintain code quality, and facilitate collaboration throughout the development cycle. Adhering to these principles ensures that the project remains reliable, maintainable, and scalable over time.

We believe that code quality is as important as its functionality. These standards are defined to ensure code readability, integrity, and sustainability. Our philosophy is based on three principles: simplicity in design, clarity in implementation, and effective collaboration.

Back to top


Code Style Guide

To reduce errors and create consistency in the code, a specific coding style is followed. This includes rules for naming variables and functions, code structure, indentation, line length, and the correct use of comments. The goal of this section is to make the code easily understandable, even for someone new to the project.

We use GDScript as the main language of the project and external modules, so most of this section will be about the recommended GDScript syntax. This section lists conventions to write better code in project. The goal is to encourage writing clean, readable code and promote consistency across modules, discussions, and tutorials.

Style guides aren't meant as hard rulebooks. At times, you may not be able to apply some of the guidelines below. When that happens, use your best judgment, and ask fellow developers for insights.

Note

Some of these items may not be observed in the current codes. You can join the contributors by correcting them based on this section!

Click to show
Here is a complete class example based on these guidelines
class_name SimpleTokenizer
extends Tokenizer
## Builti-in simple tokenizer.
##
## Provides simple tokenization system for non-complex chunks. Uses standard [Spliter]s
## for both chunking and tokenize.

# NOTE: Don't use with multiple threads, see #308

signal spliter_changed(new: Spliter)

@export var initial_state: Node
var is_active := true:
	set(value):
		if value == is_active:
			return
		is_active = value
		activation_state_changed.emit(is_active)

@onready var _spliter := Spliter.new():
	set = set_spliter
@onready var _stpliter_name: String = _spliter.name


func _init() -> void:
	add_to_group("tokenizers")


func _enter_tree() -> void:
	print("this happens before the ready method!")


func _ready() -> void:
	ConfigAPI.spliter_changed.connect(_on_spliter_changed)
	_spliter.alocate_cache()


func tokenize(text: string) -> void:
	if not _spliter:
		return

	var chunks: TextUtils.Chunk = _spliter.create_chunks(text)
	assert(chunks.is_complex == false)

	_spliter.back_to_cached()
	var chunk_cache: Spliter.Cache = _spliter.cache_chunks(chunks)
	if not chunk_cache.is_equal(_spliter.current_cache):
		_spliter.unload_current()
		await _spliter.unload_completed
		var tokens := _spliter.tokenize(chunk_cache, true, true, TokenList.new(), InternalTokenizeDatabase)
		Signals.tokenize_completed(self, tokens)


func set_spliter(value) -> void:
	_spliter = value
	_spliter_name = _spliter.name


func _on_spliter_changed(new: Spliter) -> void:
	print("spliter changed")
	spliter_changed.emit(new)


class InternalTokenizeDatabase extedns Database:
	var usages: int = 0

	func _init():
		db = DatabseUtils.load_tfdb("res://data/database/tokenizer.tfdb)

Formatting

Indentation

  • Use Tabs instead of spaces for indentation.
  • Each indent level should be one greater than the block containing it.
  • Use 2 indent levels to distinguish continuation lines from regular code blocks, exceptions to this rule are arrays, dictionaries, and enums. Use a single indentation level to distinguish continuation lines:
Example code
enum OptionTypes {
	SEPARATOR = -1,
	REGULAR,
	SUBMENU,
	CHECKBOX,
	RADIO_CHECKBOX,
}

add_child(Factory.file_dialog(FileDialog.FILE_MODE_OPEN_FILE,
		FileDialog.ACCESS_FILESYSTEM, ["*.ini,*.cfg;INI configuration file"], 
		_import_mode, true, OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS))

Trailing comma

  • Use a trailing comma on the last line in arrays, dictionaries, and enums. This results in easier refactoring and better diffs in version control as the last line doesn't need to be modified when adding new elements. Trailing commas are unnecessary in single-line lists, so don't add them in this case:
Example code
var array = [
	1,
	2,
	3,
]

var array = [1, 2, 3]

Blank lines

  • Surround functions and class definitions with two blank lines.
  • Use one blank line inside functions to separate logical sections.

Line length

  • Keep individual lines of code under 100 characters.
  • If you can, try to keep lines under 80 characters. This helps to read the code on small displays and with two scripts opened side-by-side in an external text editor. For example, when looking at a differential revision.

One statement per line

  • Avoid combining multiple statements on a single line, including conditional statements, to adhere to the GDScript style guidelines for readability. (The only exception to that rule is the ternary operator.)

Format multiline statements for readability.

  • For readability, break long if statements/ternary expressions into multiple lines with 2 indent levels. GDScript allows line wrapping with parentheses (preferred for easier refactoring) or backslashes. Place and/or keywords at the start of continued lines:
Example code
var angle_degrees = 135
var quadrant = (
		"northeast" if angle_degrees <= 90
		else "southeast" if angle_degrees <= 180
		else "southwest" if angle_degrees <= 270
		else "northwest"
)

var position = Vector2(250, 350)
if (
		position.x > 200 and position.x < 400
		and position.y > 300 and position.y < 400
):
	pass

Avoid unnecessary parentheses

  • Avoid parentheses in expressions and conditional statements. Unless necessary for order of operations or wrapping over multiple lines, they only reduce readability.

Boolean operators

  • Use and instead of &&.
  • Use or instead of ||.
  • Use not instead of !.
  • You may also use parentheses around boolean operators to clear any ambiguity. This can make long expressions easier to read.

Comment spacing

  • Regular comments (#) and documentation comments (##) should start with a space.
  • Use one # for comment out code without space.
  • Code region comments (#region/#endregion) must follow that precise syntax, so they should not start with a space.
  • Prefer writing comments on their own line as opposed to inline comments (comments written on the same line as code). Inline comments are best used for short comments, typically a few words at most.

Whitespace

  • Always use one space around operators and after commas.
  • Avoid extra spaces in dictionary references and function calls. One exception to this is for single-line dictionary declarations, where a space should be added after the opening brace and before the closing brace. This makes the dictionary easier to visually distinguish from an array, as the [] characters look close to {} with most fonts.
  • Don't use spaces to align expressions vertically.
Example code
position.x = 5
position.y = target_position.y + 10
dict["key"] = 5
my_array = [4, 5, 6]
my_dictionary = { key = "value" }
print("foo")

Quotes

  • Use double quotes unless single quotes make it possible to escape fewer characters in a given string. See the examples below:
Example code
# Normal string.
print("hello world")

# Use double quotes as usual to avoid escapes.
print("hello 'world'")

# Use single quotes as an exception to the rule to avoid escapes.
print('hello "world"')

# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
print("'hello' \"world\"")

Numbers

  • Don't omit the leading or trailing zero in floating-point numbers. Otherwise, this makes them less readable and harder to distinguish from integers at a glance.
  • Use lowercase for letters in hexadecimal numbers, as their lower height makes the number easier to read.
  • Take advantage of GDScript's underscores in literals to make large numbers more readable. (More than 1000000)

Naming conventions

These naming conventions follow the Godot Engine style. Breaking these will make your code clash with the built-in naming conventions, leading to inconsistent code.

Summary table
Type Convention Example
File names snake_case yaml_parser.gd
Class names PascalCase class_name YAMLParser
Node names PascalCase Camera3DPlayer
Functions snake_case func load_level():
Variables snake_case var particle_effect
Signals snake_case signal door_opened
Constants CONSTANT_CASE const MAX_SPEED = 200
Enum names PascalCase enum Element
Enum members CONSTANT_CASE {EARTH, WATER, AIR, FIRE}
  • Use snake_case for file names. For named classes, convert the PascalCase class name to snake_case.
  • Use PascalCase for class and node names. Also use PascalCase when loading a class into a constant or a variable.
  • Use snake_case to name functions and variables.
  • Prepend a single underscore (_) to virtual methods functions the user must override, private functions, and private variables
  • Use the past tense to name signals, or simple form for requests.
  • Write constants with CONSTANT_CASE, that is to say in all caps with an underscore (_) to separate words.
  • Use PascalCase for enum names and keep them singular, as they represent a type. Use CONSTANT_CASE for their members, as they are constants.
  • Write enums with each item on its own line. This allows adding documentation comments above each item more easily, and also makes for cleaner diffs in version control when items are added or removed.

Code order

Organize code this way
01. @tool, @icon, @static_unload
02. class_name
03. extends
04. ## doc comment

05. signals
06. enums
07. constants
08. static variables
09. @export variables
10. remaining regular variables
11. @onready variables

12. _static_init()
13. remaining static methods
14. overridden built-in virtual methods:
	1. _init()
	2. _enter_tree()
	3. _ready()
	4. _process()
	5. _physics_process()
	6. remaining virtual methods
15. overridden custom methods
16. remaining methods
17. signal callbacks
18. subclasses

And put the class methods and variables in the following order depending on their access modifiers:

1. public
2. private

We optimized the order to make it easy to read the code from top to bottom, to help developers reading the code for the first time understand how it works, and to avoid errors linked to the order of variable declarations.

This code order follows four rules of thumb:

  • Properties and signals come first, followed by methods.
  • Public comes before private.
  • Virtual callbacks come before the class's interface.
  • The object's construction and initialization functions, _init and _ready, come before functions that modify the object at runtime.

More details

  • Don't declare member variables if they are only used locally in a method, as it makes the code more difficult to follow. Instead, declare them as local variables in the method's body.
  • Declare local variables as close as possible to their first use. This makes it easier to follow the code, without having to scroll too much to find where the variable was declared.

Spacing Between Code Order Blocks

For items 05–11 in the Code Order (signals, enums, constants, static variables, @export variables, regular variables, @onready variables):

  • All members of the same group are listed consecutively with no blank lines.
    (e.g., all signal declarations in a single block without blank lines in between)
  • Exactly one blank line separates different groups to visually distinguish them.
Example code
signal started
signal finished
signal cancelled

enum State { IDLE, RUNNING, STOPPED }

const MAX_SPEED = 200
const MIN_SPEED = 10

static var _cache: Dictionary = {}

@export var speed := 100
@export var name := ""

var is_active := true
var current_state: State = State.IDLE

@onready var _timer := Timer.new()

Static typing

Use static typing as much as possible to:

  • detect more errors without even running the code
  • give you and others more information as you're working
  • improve editor autocompletion and documentation of your scripts
  • improve performance by using optimized opcodes when operand/argument types are known at compile time

With these rules:

  • To declare a variable's type, use <variable>: <type>.
  • To declare the return type of a function, use -> <type>.
  • Prefer := when the type is written on the same line as the assignment. Include the type hint when the type is ambiguous, and omit the type hint when it's redundant.
  • Don't use ...: <type> = ... or := for constants.
  • To define the type of an Array, enclose the type name in [].
  • Use type casting if necessary and plan for appropriate behavior for null value.
  • Keep lines safe as much as possible, the line number will turn green at the left of the script editor when line is safe.

Back to top


Best Practices

This section includes patterns and recommendations that enhance the overall quality of the project: proper structuring of folders and files, adherence to useful design patterns, appropriate error handling, and consideration of optimizations from the early stages of development.

Caution

This section content isn't ready.

Back to top


Documentation

All important parts of the code should have appropriate Documentation Comments so that the logic and purpose of those parts are easily understandable to other developers and contributors. Also, project documentation must be kept up to date so that any changes in the code are reflected in the documentation as well.

Caution

This section content isn't ready.

Back to top


Useful Resources & Other Links

In this section, helpful links to similar internal resources and some external resources that were considered in writing this document are introduced:

Back to top