Jump to content
Unity Insider Forum

C# - Be smart [Tipp and Tricks]


John

Recommended Posts

Heyo :lol:,

ich wollte hier ein Thread erstellen in dem es darum geht kleine aber feine Tricks zu Listen die man jeweils beim Programmieren oftmals nicht im Kopf hat und benutzen kann um smarten Code zu schreiben :D

Index

Basics

 

#1. Der If-Else Terminator

Source-Code:

Before:

//...
[SerializeField]
private GameObject _obj1;
[SerializeField]
private GameObject _obj2;
[SerializeField]
private GameObject _obj3;
[SerializeField]
private GameObject _obj4;

//...
if (_obj1.activeSelf)
{
	_obj1.SetActive(false);
}
if (_obj2.activeSelf)
{
	_obj2.SetActive(false);
}
if (_obj3.activeSelf)
{
	_obj3.SetActive(false);
}
if (_obj4.activeSelf)
{
	_obj4.SetActive(false);
}
//...

After:

//...
[SerializeField]
private GameObject _obj1;
[SerializeField]
private GameObject _obj2;
[SerializeField]
private GameObject _obj3;
[SerializeField]
private GameObject _obj4;

//...
var objs = new GameObject[] {_obj1, _obj2, _obj3, _obj4};
foreach (var obj in objs)
{
	if (obj.activeSelf)
	{
		obj.SetActive(false);
	}
}
//...

Detail-Erklärung:

Oftmals besitzt man eine feste Anzahl an Variablen die man gerne ändern möchte. Nicht immer sind die in einem Array und darum passiert es schnell das man eine endlose "if-else"-Abfrage hat in dem man jeweils die Variable überprüft und dan eine action ausführt. Da können sich schnell Fehler einschleichen oder das ändern bzw. hinzufügen neue Sachen ist oftmals sehr mühselig.

Tipp:

Erstellt einen temporären Array in dem ihr alle Objects zusammenfasst und lässt anschließend ein For-/-each-Loop rüber laufen und tada ihr hab alles zusammengefasst und müsst nur noch in diesem Abschnitt was ab ändern und es wird sofort auf alle Objects übernommen. So seid ihr auf der sicheren Seite was Änderungen angeht.

Mfg.

-John

Link zu diesem Kommentar
Auf anderen Seiten teilen

Schönes Beispiel für eine solche Nutzung, wobei man im Beispiel eigentlich auch gleich schreiben kann:

//...
[SerializeField]
private GameObject[] _allObjects = new GameObject[3];
//...

foreach (GameObject obj in _allObjects)
{
   if (obj.activeSelf) obj.SetActive(false);
}

Das geht natürlich nur bedingt, wenn du eine externe Klasse einbinden musst, die Variablen bereits einzeln übergibt. Aber wenn Variablen eh keine Aussagekraft besitzen (z.b. "Image1", "Image2" etc), dann würde ich sie gleich in ein Array packen.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Zer0Cool & LifeIsGood ihr hab völlig recht in fall das man von dem gleichen Object eine Collection besitzt sollte man auf ein Array zurückgreifen und das jeweils mit eine SerializeField versehen wird. Aber ich gibt auch fälle wo man keine Collection jeweils davon besitzen möchte :D Aber der beste Hinweis ist wie @Zer0Cool gesagt hat "item01, item02, item3..." wen das auftaucht sollte man darüber nachdenken eine Collection zu machen.

 

Ich habe nochmal ein Beispiel erstellt um es besser zu erklären ;)

//...
[SerializeField]
private int _goldAmount;
[SerializeField]
private int _diamontAmount;
[SerializeField]
private int _rubyAmount;
[SerializeField]
private int _perlAmount;

//...
var amounts = new int[] {_goldAmount, _diamontAmount, _rubyAmount, _perlAmount};
foreach (var amount in amounts)
{
	if(amount < 0)
	{
		signal.Publish<NegativAmountEvent>();
	}
}
//...

In der Klasse selbst hat man jeweils noch andere Provider oder Behaviours die direkt mit den Variable z.b. individuell arbeiten.

Link zu diesem Kommentar
Auf anderen Seiten teilen

#2. Extract If-Condition in Methods

Before:

//...
private void OnRequestJoinSession()
{
	if (_playerData.HasEquipKey
		&& _playerData.EquipKeyId == _sessionData.RequiredKeyId
		&& _playerData.Level > _sessionData.RequiredPlayerLevel)
	{
		//JoinSession...
	}
}
//...

After:

//...
private void OnRequestJoinSession()
{
	if (IsPlayerValid())
	{
		//JoinSession...
	}
}

private bool IsPlayerValid()
{
	return _playerData.HasEquipKey
		&& _playerData.EquipKeyId == _sessionData.RequiredKeyId
		&& _playerData.Level > _sessionData.RequiredPlayerLevel;
}
//...

Detail-Erklärung:

Ich habe schon oft erlebt das man meistens eine If-Condition hat die ein wenig mehr Logik erfordert. Oftmals schleichen sich hier schnell Fehler ein oder das lesen dieser Condition ist meistens mühselig. Für diese Fälle ist es praktisch diese in eine separate Methode zu verlagern um einerseits den Lesefluss zu erleichtern und auf der anderen Seite Fehler vorzubeugen. Falls man in seiner Klasse zum Beispiel öfters diese Condition überprüfen muss lohnt es sich auf jedenfalls diese in eine separate Methode zu Verlagen.

Tipp:

Ich benutze die Faustregel oft falls ich eine Condition habe die mehr als ein State überprüft verlagere ich diese in eine andere Methode. Oftmals macht es auch Sinn selbst bei eine Condition diese in einer separaten Methode zu verlagern z.B. bei größeren Überprüfungen. Es erleichtert das ab ändern später falls sich die Condition ändert und verbessert den Lesefluss des source-codes

Zusatz-Beispiel:

//...
private bool IsNumberNotZero()
{
	return int.Parse(_uiText.text) != 0;
}
//...

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Find ich gut, ein andere Methode wäre sehr konsequent auf die Bezeichner zu achten und den Klassen der Objekte selbst Methoden für die Prüfung an die Hand zu geben:

private void OnRequestJoinSession()
{
	if (m_currentPlayerData.HasEquipKey() &&
	    m_currentPlayerData.EquipKeyIdEquals(m_CurrentSessionData.RequiredKeyId) &&
	    m_currentSessionData.CheckRequiredPlayerLevel(m_currentPlayerData.Level))
	{
		//JoinSession...
	}
}

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

#3. Simple Namings: Keine Type Beschreibung oder Falsch Information

Before:

//...
private GameObject[] _unitList;
//...
private List<int> _scores;
//...
private GameObject _player01;
//...
private GameObject _player02;
//...

After:

//...
private GameObject[] _units;
//...
private List<int> _lastScoreCollection;
//...
private PlayerContainer _playerContainer;
//...

Detail-Erklärung:

Ein richtiger Variablen-Name zu finden kann oftmals eine riesige Hürde sein und dann passiert es sehr schnell das man seinen Variablen-Name verkompliziert. Doch wie kann man dagegen angehen und welchen Trick gibt es für eine schnellere Findung von Variablen-Namen. 

Oftmals hilft es sogar wie im Falle von den units statt "-List" hinschreibt könnte man das ganze einfach sauber gestellten in dem man den Namen unit in die Mehrzahl verwandet units damit hat man schon Information geben das es sich hier nicht um eine einzelne Unit, sondern um eine Ansammlung von Units handelt.

In dem Falle von scores ist es zwar jetzt nicht aussagekräftig nur ein "-s" dran zu hängen. Hier kann man auch auf Hilf Wörter zurückgreifen die das ganze mehr unterstreichen. Wie zum Beispiel "-Collection" das gibt darüber Auskunft das die letzte Score Zusammen Collected werden und man mit Hilfe dieser die letzte Score sich holen kann.

Wichtiger Hinweis: Sicherlich wird sich jetzt der ein oder andere fragen: Was jetzt ein Unterschied macht wen ich „List“ hinschreibe anstatt von einer Collection?

Es könnte schnell dazu führen das andere Leser oder man selbst verwirrt ist. Falls man jeweils mit dieser variable arbeitet und diese aus besonderen Gründen von einer List<int> in ein Array von int’s ändern müsste könnte der Leser erwarten, dass er eine Liste bekommt. Darum ist es ideal wen man einen Namen nimmt der jetzt nicht auf einen Typen zurückschließt lässt.

Container ist ebenfalls ein oftmals Benutzer Name den einen öfters über den Weg läuft. Falls man zum Beispiel mehrere Spieler besitzt und diese gerne zusammenfassen will kann man dafür sogar eine extra Klasse anfertigen die diese hält und verwaltet. Ich benutze gerne den Namen Container wen ich in dieser Klasse eine Ansammlung von verschieden Typen habe.

Bei einer Collection erwarte ich jeweils eine Ansammlung exakt nur vom einem Typen und nichts Anderes. Im Gegenteil zu einem Container wo ich alles reinpacken kann und diese dort auf eine bestimmte Zeit lagern kann.

Tipp:

Oftmals sollte man variablen nicht verkomplizieren nur, weil sie cool klingen oder glatt unnötige Information liefern wie z.B. den Typen den in den Namen reinschreiben das ist total überflüssig da fast jede der IDE bereits diesen Job übernehmen und dadurch nur mehr Verwirrung gestiftet werden kann.

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

#4. Das Zwiebel-Prinzip / News-Paper

Before:

//...		
private void OnGUI()
{
	EditorGUILayout.HelpBox(_infoMessage, MessageType.Info);
		_displayAdvancedSettings = EditorGUILayout.Foldout(_displayAdvancedSettings, "Advance Settings:");
	if (_displayAdvancedSettings)
	{
		_customizeXValue = EditorGUILayout.IntField(_customizeXValue, "Important Value");
		_selectedColor = EditorGUILayout.ColorField("Selected Color", _selectedColor);
		_customizeYValue = EditorGUILayout.FloatField(_customizeYValue, "Important Value");
	}
	if (GUILayout.Button("Execute... Something"))
	{
		ExecuteCalculation();
	}
	GUILayout.Label("Process Time" + _processTime);
}
//...

After:

//...		
private void OnGUI()
{
	DrawHelperHeader();
	DrawSettingsActivator();
	if (_displayAdvancedSettings)
	{
		DrawAdvancedSettings();
	}
	DrawExecuteArea();
}

private void DrawHelperHeader()
{
	EditorGUILayout.HelpBox(_infoMessage, MessageType.Info);
}

private void DrawSettingsActivator()
{
	_displayAdvancedSettings = EditorGUILayout.Foldout(_displayAdvancedSettings, "Advance Settings:");
}

private void DrawAdvancedSettings()
{
	_customizeXValue = EditorGUILayout.IntField(_customizeXValue, "Important Value");
	_selectedColor = EditorGUILayout.ColorField("Selected Color", _selectedColor);
	_customizeYValue = EditorGUILayout.FloatField(_customizeYValue, "Important Value");
}

private void DrawExecuteArea()
{
	if (GUILayout.Button("Execute... Something"))
	{
		ExecuteCalculation();
	}
	GUILayout.Label("Process Time" + _processTime);
}
//...

Detail-Erklärung:

Der Aufbau einer Kasse und deren Struktur ist ein wichtiger Bestandteil der oftmals in Vergessenheit gerat. Eine geordnete und saubere Klasse ist das fundamental für einen guten Lesefluss und Verständnis für eine Klasse. Doch wie sieht eine saubere Struktur eigentlich aus? Ein seltsames Phänomen das mir immer über den Weg läuft ist das öfters in der "OnGUI", "Update", "Start"... MonoBehaviour-Klassen auftreten. Dort wird oftmals alles was man möchtet in diese Methode reingeschrieben. Hier ist es von enormen Vorteil dieses in unterschiedliche Methoden zu Verlagen, da falls man Änderungen vornehmen will sich nicht erst durch den gewaltigen GUI-Code durchkämpfen muss, sondern man schaut sich jeweils einmal die OnGUI-Klasse an wo jeweils das UI-Behaviour beschrieben wird und springt dann anschließend in die jeweilige Methode in der man eine Änderung vornehmen will. Man erspart sich Zeit und erleichtert das Lesen und das Verständnis von Methoden. Diese Concept kann man auch jeweils für seinen Klassen-Aufbau anwenden.

//...

public class ...
{
	//Public-Variablen
  	//...(static, const,...)
  	//Private-Variablen
  	//...(static, const,...)
  
  	//Constructor
	//...(init, setup)
  
	//Public-Methoden
  	//...(static, override, virtual,...)
  	//Private-Methoden
  	//...(static, override, virtual,...)
  
	//Destructor
	//...(cleanup, destory)
}
//...

Oftmals wird auch der Vergleich vom Zeitung-Lesen genommen. Das man jeweils seine Header hat darunter folgen die Header2 danach folgt Letzt endlich der Artikel-Content. So kann man schon vorab abklären was einem gefällt bzw. was man sucht und dann zu dem jeweiligen Artikel-Content schneller navigieren ohne von Anfang sich durch jeden Artikel Content zu kämpfen bis man zu seiner passenden Stelle trifft.

Tipp:

Ich verfolge stets das Ziel den Leser meiner Klasse Oberhalb meiner Klasse darüber Aufzuklären was in dieser Klasse passiert und unterhalb findet er jeweils die tatsächliche Implementierung.

//...
// Top
public bool IsObjectInRange(object obj)
{
  return _playerRange < GetPlayerDistanceFromTarget(obj);
}

// Bottom
private float GetPlayerDistanceFromTarget(object obj)
{
  return Vector3.Distance(_playerObj, obj);
}
//...

Eine weitere Faustregel die man verfolgen kann in Public-Methoden beschreibe ich nur jeweils das Ausgeführte Behaviour das ich tatsächlich in den Private-Methoden ausführe. So bekomme ich schneller einen Überblick und Verständnis der Klasse. Wen ich von außen in diese Klasse springe und falls mich wirklich die Implementierung interessiert spring ich jeweils dann ein schritt tiefer zu den Private-Methoden.

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Lädt...
×
×
  • Neu erstellen...