Code Best Practices: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Text replacement - "\[\[SQF[ _]syntax" to "[[SQF Syntax")
m (Text replacement - "\[ *(https?:\/\/[^ = ]+) +([^= ]+) *\]" to "{{Link|$1|$2}}")
 
(14 intermediate revisions by the same user not shown)
Line 11: Line 11:
=== Prerequisites ===
=== Prerequisites ===


* An advanced text editor such as Visual Studio Code or Notepad++ - see [[:Category:Community_Tools#Code_Edition|Community Tools - Code Edition]] for useful tools and plugins
* An advanced text editor such as Visual Studio Code or Notepad++ - see {{Link|:Category:Community Tools#Code Edition}} for useful tools and plugins
* Some English knowledge, helped if needed by a translator tool such as [https://www.google.com/translate Google Translate] or [https://www.deepl.com/ DeepL]
* Some English knowledge, helped if needed by a translator tool such as {{Link|https://www.google.com/translate|Google Translate}} or {{Link|https://www.deepl.com/|DeepL}}
* Access to this wiki is a plus (the Swiss flag is, too)
* Access to this wiki is a plus (the Swiss flag is, too)
* Tutorials/examples (YouTube tutorials, community tutorials, [[:Category:Example_Code|Example Code]])
* Tutorials/examples (YouTube tutorials, community tutorials, [[:Category:Example_Code|Example Code]])
* Google-Fu (a.k.a search engine skills)
* Google-Fu (a.k.a search engine skills)
* {{arma3}} Discord channel: [https://discord.gg/arma ARMA], channel <tt>#scripting</tt>
* {{arma}} Discord server: {{Link|https://discord.gg/arma|ARMA}}, channel {{hl|#arma3_scripting}}




Line 26: Line 26:
* Choose an indentation format '''and stick to it'''. There is not especially one indent better than another (''but there sure are terrible ones''), again the important point here being ''consistency''.
* Choose an indentation format '''and stick to it'''. There is not especially one indent better than another (''but there sure are terrible ones''), again the important point here being ''consistency''.
** Common indentation styles are:
** Common indentation styles are:
*** [https://en.wikipedia.org/wiki/Indentation_style#K&R_style K&R style] indenting
*** {{Link|https://en.wikipedia.org/wiki/Indentation_style#K&R_style|K&R style}} indenting
*** [https://en.wikipedia.org/wiki/Indentation_style#Allman_style Allman style] indenting
*** {{Link|https://en.wikipedia.org/wiki/Indentation_style#Allman_style|Allman style}} indenting
** Use empty space. Line return, spaces before and after brackets, if this improves readability, use it: space is free.
** Use empty space. Line return, spaces before and after brackets, if this improves readability, use it: space is free.
** indent with two/four spaces '''or''' one tab. Do not mix these.
** indent with two/four spaces '''or''' one tab. Do not mix these.
** ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
** ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
* {{ic|<span style{{=}}"background-color: #FCC">'''0 {{=}}''' </span>''myCommand''}} is "useful" only for editor fields that for no apparent reason refuse commands returning a value. You do '''not''' need it in script files.
* {{hl|1= <span style="background-color: #FCC">'''0 =''' </span>''myCommand''}} '''was''' "useful" only for editor fields that for no apparent reason refused commands returning a value. You do '''not''' need it in script files, and don't need it in editor fields anymore '''since {{arma3}} v2.04'''.


=== Variable format ===
=== Variable format ===
Line 38: Line 38:
--> While [[SQF Syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
--> While [[SQF Syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
** Your variable names must have a meaning: variables like '''_u''' instead of '''_uniform''' should not exist. '''_i''' is an accepted iteration variable name (e.g in [[for]] loops).
** Your variable names must have a meaning: variables like '''_u''' instead of '''_uniform''' should not exist. '''_i''' is an accepted iteration variable name (e.g in [[for]] loops).
** It is recommended to use [https://en.wikipedia.org/wiki/Camel_case camel-case] your variables;<!--
** It is recommended to use {{Link|https://en.wikipedia.org/wiki/Camel_case|camel-case}} your variables;<!--
--> camel-casing (namingLikeThis) your variables and functions make the code naturally more readable, especially for long names.
--> camel-casing (namingLikeThis) your variables and functions make the code naturally more readable, especially for long names.
* Prefix your public variables and [[setVariable]] with your tag in order to avoid any conflict about other mods, scripts or mission variables.
* Prefix your public variables and [[setVariable]] with your tag in order to avoid any conflict about other mods, scripts or mission variables.
* Make your variables '''private''' thanks to the [[private]] or [[params]] keyword in order to avoid accidental upper-scope overwriting of variables of the same name.
* Make your variables '''private''' thanks to the [[private]] or [[params]] keyword in order to avoid accidental upper-scope overwriting of variables of the same name.
* Defined constants must be UPPERCASE_WITH_UNDERSCORES (e.g {{ic|#define SOME_CONST}}).
* Defined constants must be UPPERCASE_WITH_UNDERSCORES (e.g <sqf inline>#define SOME_CONST</sqf>).




Line 48: Line 48:


* '''DRY:''' '''D'''on't '''R'''epeat '''Y'''ourself. If you write the same code block or the same logic many times, export this code as a function and use parameters with it.
* '''DRY:''' '''D'''on't '''R'''epeat '''Y'''ourself. If you write the same code block or the same logic many times, export this code as a function and use parameters with it.
** If your code has too many levels, it is time to split and rethink it<!--
** If your code has too many levels, it is time to split and rethink it - maybe using [[exitWith]] (e.g <sqf inline>if (a) then { if (b) then { if (c) then { /* etc */ }; }; };</sqf>)
--> (e.g {{ic|[[if]] (a) [[then]] { [[if]] (b) [[then]] { [[if]] (c) [[then]] { {{codecomment|/* etc */ }} }; }; };}}...)
** Do NOT use [[PreProcessor Commands#Macros|macros]] as functions - these hinder readability. Use functions instead.
** Do NOT use [[PreProcessor Commands#Macros|macros]] as functions - these hinder readability. Use functions instead.
* {{cc|Comments}} in your code must not explain ''what'' the code does, but ''why'' it is done this way (if needed).<!--
* {{cc|Comments}} in your code must not explain ''what'' the code does, but ''why'' it is done this way (if needed).<!--
Line 59: Line 58:
** One Functions directory, with sub-directories if needed.
** One Functions directory, with sub-directories if needed.
* Use [[Event Scripts]] as needed.
* Use [[Event Scripts]] as needed.
* Don't put '''any code''' in a unit's init box ''but eventually'' [[local]] commands for this specific unit - '''all''' unit's init boxes are run client-side on client connection.
* Don't put '''any code''' in a unit's init box ''but eventually'' [[Multiplayer Scripting#Locality|local]] commands for this specific unit - '''all''' unit's init boxes are run client-side on client connection.




Line 68: Line 67:
-->
-->
{| class="wikitable"
{| class="wikitable valign-top"
! <big>Bad Example</big>
! <big>Bad Example</big>
! <big>Good Example</big>
! <big>Good Example</big>
Line 77: Line 76:
</small>
</small>
|-
|-
| <code>_unit = player;</code>
|
| <code>'''private''' _unit = player;</code>
_unit = player;
|
'''private''' _unit = player;
|-
|-
| <code>private _uB = allUnits select { side _x == blufor };</code>
|
| <code>private '''_bluforUnits''' = allUnits select { side _x == blufor };</code>
private _uB = allUnits select { side _x == blufor };
|
private '''_bluforUnits''' = allUnits select { side _x == blufor };
|-
|-
| <code>private _plead = leader player;</code>
|
| <code>private '''_playersLeader''' = leader player;</code>
private _plead = leader player;
|
private '''_playersLeader''' = leader player;
|-
|-
| <code>finalAssault = true; publicVariable "finalAssault";</code>
|
| <code>'''PREFIX_'''finalAssault = true; publicVariable "'''PREFIX_'''finalAssault";</code>
finalAssault = true; publicVariable "finalAssault";
|
'''PREFIX_'''finalAssault = true; publicVariable "'''PREFIX_'''finalAssault";
|-
|-
| <code>player setVariable ["MoneyInPocket", 250, true];</code>
|
| <code>player setVariable ["'''PREFIX_'''MoneyInPocket", 250, true];</code>
player setVariable ["MoneyInPocket", 250, true];
|
player setVariable ["'''PREFIX_'''MoneyInPocket", 250, true];
|-
|-
|
|
<code>#define KILL(UNIT) UNIT setDamage 1<br>
#define KILL(UNIT) UNIT setDamage 1
(...)<br>
{
// ...
KILL(_x);
} forEach (units group player - [player]);</code>
{
KILL(_x);
} forEach (units group player - [player]);
|
|
<code>private _killFnc = { _this setDamage 1; };<br>
private _killFnc = { _this setDamage 1; };
(...)<br>
{
// ...
_x call _killFnc;
} forEach (units group player - [player]);</code>
{
_x call _killFnc;
} forEach (units group player - [player]);
|- style="vertical-align: top"
|- style="vertical-align: top"
|
|
<code>{{cc|if player has less than 3/4 health}}
{{cc|if player has less than 3/4 health}}
if (damage player > 0.25) then
if (damage player > 0.25) then
{
{
{{cc|if the player has no first aid kit}}
{{cc|if the player has no first aid kit}}
if (not ("FirstAidKit" in items player))
if (not ("FirstAidKit" in items player))
{
{
{{cc|if player has room for first aid kit}}
{{cc|if player has room for first aid kit}}
if (player canAdd "FirstAidKit") then
if (player canAdd "FirstAidKit") then
{
{
{{cc|add first aid kit to the player}}
{{cc|add first aid kit to the player}}
player addItem "FirstAidKit";
player addItem "FirstAidKit";
}
}
else
else
{
{
{{cc|set player's damage to 1/4}}
{{cc|set player's damage to 1/4}}
player setDamage 0.25;
player setDamage 0.25;
};
};
};
};
};</code>
};
|
|
<code>{{cc|player will need health at this stage of the mission}}
{{cc|player will need health at this stage of the mission}}
if (
if (
damage player > 0.25 &&
damage player > 0.25 &&
not ("FirstAidKit" in items player)) then
not ("FirstAidKit" in items player)) then
{
{
if (player canAdd "FirstAidKit") then
if (player canAdd "FirstAidKit") then
{
{
player addItem "FirstAidKit";
player addItem "FirstAidKit";
}
}
else {{cc|let's help him anyway}}
else {{cc|let's help him anyway}}
{
{
player setDamage 0.25;
player setDamage 0.25;
};
};
};</code>
};
|-
|-
! colspan="2" |
! colspan="2" |
Line 145: Line 158:
=== Flow Logic examples ===
=== Flow Logic examples ===
</small>
</small>
|- style="vertical-align: top"
|-
|
if (alive player && damage player >= 0.9) then {
hint "very damaged";
};
if (alive player && damage player >= 0.5 && damage player < 0.9) then {
hint "quite damaged";
};
if (alive player && damage player > 0 && damage player < 0.5) then {
hint "slightly damaged";
};
if (alive player && damage player == 0) then {
hint "pristine state";
};
if (not alive player) then {
hint "dead";
};
|
if (not alive player) exitWith { hint "dead"; };
private _playerDamage = damage player;
switch (true) do {
case (_playerDamage >= 0.9): { hint "very damaged"; };
case (_playerDamage >= 0.5): { hint "quite damaged"; };
case (_playerDamage > 0)  : { hint "slightly damaged"; };
default { hint "pristine"; };
};
|-
|
|
<code>if (alive player && damage player >= 0.9) then {
if (cond1) then
hint "very damaged";
{
};
if (cond2) then
if (alive player && damage player >= 0.5 && damage player < 0.9) then {
{
hint "quite damaged";
if (cond3) then
};
{
if (alive player && damage player > 0 && damage player < 0.5) then {
// hadouken code
hint "slightly damaged";
}
};
else { {{codecomment|/* failure 3 */}} };
if (alive player && damage player == 0) then {
}
hint "pristine state";
else { {{codecomment|/* failure 2 */}} };
};
}
if (not alive player) then {
else { {{codecomment|/* failure 1 */}} };
hint "dead";
};</code>
|
|
<code>if (not alive player) exitWith { hint "dead"; };<br>
if (!cond1) exitWith { {{codecomment|/* failure 1 */}} };
private _playerDamage = damage player;
if (!cond2) exitWith { {{codecomment|/* failure 2 */}} };
switch (true) do {
if (!cond3) exitWith { {{codecomment|/* failure 3 */}} };
case (_playerDamage >= 0.9): { hint "very damaged"; };
case (_playerDamage >= 0.5): { hint "quite damaged"; };
// code
case (_playerDamage > 0)  : { hint "slightly damaged"; };
default { hint "pristine"; };
};</code>
|-
|-
! colspan="2" |
! colspan="2" |
Line 176: Line 211:
=== Format examples ===
=== Format examples ===
</small>
</small>
|- style="vertical-align: top"
|-
|
|
<code>if (not alive player) exitWith  
if (not alive player) exitWith  
{ hint "dead"; };<br>
{ hint "dead"; };
private _playerDamage = damage player;
switch (true) do {
private _playerDamage = damage player;
case (_playerDamage >= 0.9): {hint "very damaged";};
switch (true) do {
case (_playerDamage >= 0.5): {
case (_playerDamage >= 0.9): {hint "very damaged";};
hint  "quite damaged";};<br>
case (_playerDamage >= 0.5): {
case (_playerDamage > 0): { hint "slightly damaged"; };
hint  "quite damaged";};
<nowiki>  </nowiki>default
{<br>
case (_playerDamage > 0): { hint "slightly damaged"; };
<nowiki>   </nowiki>hint "pristine";
<nowiki>  </nowiki>default
<nowiki>  </nowiki>};
{
};</code>
<nowiki>   </nowiki>hint "pristine";
<nowiki>  </nowiki>};
};
|
|
<code>if (not alive player) exitWith { hint "dead"; };<br>
if (not alive player) exitWith { hint "dead"; };
private _playerDamage = damage player;
switch (true) do {
private _playerDamage = damage player;
case (_playerDamage >= 0.9): { hint "very damaged"; };
switch (true) do {
case (_playerDamage >= 0.5): { hint "quite damaged"; };
case (_playerDamage >= 0.9): { hint "very damaged"; };
case (_playerDamage > 0)  : { hint "slightly damaged"; };
case (_playerDamage >= 0.5): { hint "quite damaged"; };
default { hint "pristine"; };
case (_playerDamage > 0)  : { hint "slightly damaged"; };
};</code>
default { hint "pristine"; };
};
|}
|}


Line 206: Line 245:


* Remember: '''consistency''' is the most important thing!
* Remember: '''consistency''' is the most important thing!
* The {{arma3}} Discord channel and its community can help: [https://discord.gg/arma ARMA]
* The {{arma}} Discord server and its community can help: {{Link|https://discord.gg/arma|ARMA}} (channel {{hl|#arma3_scripting}})
* Learn from others' scripts but don't steal code and pretend it is yours — be a decent human being. Stealing code and its consequences:
* Learn from others' scripts but don't steal code and pretend it is yours — be a decent human being. Stealing code and its consequences:
** It soils your reputation and devaluates your actions once it is found out — and it ''always'' get found out. DMCA's are filled on Steam every day.
** It soils your reputation and devaluates your actions once it is found out — and it ''always'' get found out. DMCA's are filled on Steam every day.
Line 212: Line 251:
* Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
* Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
** Obfuscated code only makes it ''harder'' to get, but does not make it ''copy-protected''. If the engine can read it, it can be obtained.
** Obfuscated code only makes it ''harder'' to get, but does not make it ''copy-protected''. If the engine can read it, it can be obtained.
** Obfuscated code is also slower on compilation (and, depending on the quality of code and obfuscation, on execution too).
** Obfuscated code is also slower on parsing and, depending on the quality of code and obfuscation, on execution too.
* Do not hesitate to help newcomers even if you don't know the whole topic: both sides will learn from it!
* Do not hesitate to help newcomers even if you don't know the whole topic: both sides will learn from it!
* Most of all, have fun!
* Most important of all, have fun!





Latest revision as of 16:08, 28 April 2023

Getting started

In the domain of development, any rule is a rule of thumb. If a rule states for example that it is better that a line of code doesn't go over 80 characters, it doesn't mean that any line must not go over 80 characters; sometimes, the situation needs it. If you have a good structure, do not change your code to enforce a single arbitrary rule. If you break many of them, you may have to change something; again, this is according to your judgement.

With that being said, let's go!

Prerequisites

  • An advanced text editor such as Visual Studio Code or Notepad++ - see Category:Community Tools - Code Edition for useful tools and plugins
  • Some English knowledge, helped if needed by a translator tool such as Google Translate or DeepL
  • Access to this wiki is a plus (the Swiss flag is, too)
  • Tutorials/examples (YouTube tutorials, community tutorials, Example Code)
  • Google-Fu (a.k.a search engine skills)
  • Arma Discord server: ARMA, channel #arma3_scripting


Best practices

Code format

  • Whatever you do about your code's format, be consistent.
  • Choose an indentation format and stick to it. There is not especially one indent better than another (but there sure are terrible ones), again the important point here being consistency.
    • Common indentation styles are:
    • Use empty space. Line return, spaces before and after brackets, if this improves readability, use it: space is free.
    • indent with two/four spaces or one tab. Do not mix these.
    • One-lining (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
  • 0 = myCommand was "useful" only for editor fields that for no apparent reason refused commands returning a value. You do not need it in script files, and don't need it in editor fields anymore since Arma 3 v2.04.

Variable format

  • Name your variables and functions properly: While SQF is (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
    • Your variable names must have a meaning: variables like _u instead of _uniform should not exist. _i is an accepted iteration variable name (e.g in for loops).
    • It is recommended to use camel-case your variables; camel-casing (namingLikeThis) your variables and functions make the code naturally more readable, especially for long names.
  • Prefix your public variables and setVariable with your tag in order to avoid any conflict about other mods, scripts or mission variables.
  • Make your variables private thanks to the private or params keyword in order to avoid accidental upper-scope overwriting of variables of the same name.
  • Defined constants must be UPPERCASE_WITH_UNDERSCORES (e.g #define SOME_CONST).


Code structuration

  • DRY: Don't Repeat Yourself. If you write the same code block or the same logic many times, export this code as a function and use parameters with it.
    • If your code has too many levels, it is time to split and rethink it - maybe using exitWith (e.g if (a) then { if (b) then { if (c) then { /* etc */ }; }; };)
    • Do NOT use macros as functions - these hinder readability. Use functions instead.
  • // Comments in your code must not explain what the code does, but why it is done this way (if needed). Your code organisation combined to your variable names must be enough to be read by a human.

Code/Files organisation

  • Use CfgFunctions to declare the functions that will be called frequently.
    • One Functions directory, with sub-directories if needed.
  • Use Event Scripts as needed.
  • Don't put any code in a unit's init box but eventually local commands for this specific unit - all unit's init boxes are run client-side on client connection.


Examples

Bad Example Good Example

Good Practice examples

_unit = player;
private _unit = player;
private _uB = allUnits select { side _x == blufor };
private _bluforUnits = allUnits select { side _x == blufor };
private _plead = leader player;
private _playersLeader = leader player;
finalAssault = true; publicVariable "finalAssault";
PREFIX_finalAssault = true; publicVariable "PREFIX_finalAssault";
player setVariable ["MoneyInPocket", 250, true];
player setVariable ["PREFIX_MoneyInPocket", 250, true];
#define KILL(UNIT) UNIT setDamage 1

// ...

{
	KILL(_x);
} forEach (units group player - [player]);
private _killFnc = { _this setDamage 1; };

// ...

{
	_x call _killFnc;
} forEach (units group player - [player]);
// if player has less than 3/4 health
if (damage player > 0.25) then
{
	// if the player has no first aid kit
	if (not ("FirstAidKit" in items player))
	{
		// if player has room for first aid kit
		if (player canAdd "FirstAidKit") then
		{
			 // add first aid kit to the player
			player addItem "FirstAidKit";
		}
		else
		{
			// set player's damage to 1/4
			player setDamage 0.25;
		};
	};
};
// player will need health at this stage of the mission
if (
	damage player > 0.25 &&
	not ("FirstAidKit" in items player)) then
{
	if (player canAdd "FirstAidKit") then
	{
		player addItem "FirstAidKit";
	}
	else // let's help him anyway
	{
		player setDamage 0.25;
	};
};

Flow Logic examples

if (alive player && damage player >= 0.9) then {
	hint "very damaged";
};
if (alive player && damage player >= 0.5 && damage player < 0.9) then {
	hint "quite damaged";
};
if (alive player && damage player > 0 && damage player < 0.5) then {
	hint "slightly damaged";
};
if (alive player && damage player == 0) then {
	hint "pristine state";
};
if (not alive player) then {
	hint "dead";
};
if (not alive player) exitWith { hint "dead"; };

private _playerDamage = damage player;
switch (true) do {
	case (_playerDamage >= 0.9): { hint "very damaged"; };
	case (_playerDamage >= 0.5): { hint "quite damaged"; };
	case (_playerDamage > 0)   : { hint "slightly damaged"; };
	default { hint "pristine"; };
};
if (cond1) then
{
	if (cond2) then
	{
		if (cond3) then
		{
			// hadouken code
		}
		else { /* failure 3 */ };
	}
	else { /* failure 2 */ };
}
else { /* failure 1 */ };
if (!cond1) exitWith { /* failure 1 */ };
if (!cond2) exitWith { /* failure 2 */ };
if (!cond3) exitWith { /* failure 3 */ };

// code

Format examples

if (not alive player) exitWith 
{ hint "dead"; };

private _playerDamage = damage player;
switch (true) do {
case (_playerDamage >= 0.9): {hint "very damaged";};
case (_playerDamage >= 0.5): {
	hint  "quite damaged";};

case (_playerDamage > 0): { hint "slightly damaged"; };
  default
{

   hint "pristine";
  };
};
if (not alive player) exitWith { hint "dead"; };

private _playerDamage = damage player;
switch (true) do {
	case (_playerDamage >= 0.9): { hint "very damaged"; };
	case (_playerDamage >= 0.5): { hint "quite damaged"; };
	case (_playerDamage > 0)   : { hint "slightly damaged"; };
	default { hint "pristine"; };
};


Final words

  • Remember: consistency is the most important thing!
  • The Arma Discord server and its community can help: ARMA (channel #arma3_scripting)
  • Learn from others' scripts but don't steal code and pretend it is yours — be a decent human being. Stealing code and its consequences:
    • It soils your reputation and devaluates your actions once it is found out — and it always get found out. DMCA's are filled on Steam every day.
    • It makes people in the community get less helpful and more reluctant in giving advices. It can also prevent them to release an interesting feature!
  • Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
    • Obfuscated code only makes it harder to get, but does not make it copy-protected. If the engine can read it, it can be obtained.
    • Obfuscated code is also slower on parsing and, depending on the quality of code and obfuscation, on execution too.
  • Do not hesitate to help newcomers even if you don't know the whole topic: both sides will learn from it!
  • Most important of all, have fun!


See also