This section is intended to provide guidelines for how to write code in the V-HAB project including bad and good examples of code sections. Please note that your supervisor may tell you to rework your code before helping you if you do not follow these guidelines (help others to help yourself (Zwinkern))


V-HAB relies heavily on object oriented programming (OOP). As this is a very generic programming principle, any good guide explaining OOP can be read if the underlying concepts of OOP are unknown.

Each programming language does implement OOP in a different way, and Matlab is not just no exception, but includes some rather peculiar implementation details. Therefore, even if you know OOP from C++, Java or the likes, do read some Matlab OOP documentation.

General Coding Guidelines

  • Create small classes with an explicitly defined purpose and a clearly defined API
    • Really, really THINK about what data (attributes) needs to be stored where, i.e. which object contains what data and which object needs to "know" what other object.
    • Try to reduce the knowledge that one object needs to have about other objects. Often, it is better to write objects that actually don't know much but themselves, and then write a higher-level object that "wires up" two other objects.
    • Therefore, think about the APIs of your objects. What's the purpose of your object, what does it need to know to fulfill this, and how should your object know about these things?
    • Getting the data structures behind your code makes it much more logical, and often leads to less code that needs to be written.
    • Often, it makes sense to separate "concerns". I.e. if you have one class that handles both matter and thermal issues, it might probably be better to split this class up into two classes and define a good API between those two.
    • It is always easier to merge two classes if you realize they're tightly coupled, than to split up one class into two.
  • Try to keep the code short!
    • More code means more bugs. Period.
    • Use Matlab build-in functions, use cells and structs including dynamic addressing of keys, move code to an own function/helper before repeating it, etc.
    • Re-use existing coding concepts, classes, functionalities. Don't introduce a new logic or concept if there's no good reason. If the classes are nice and small and well-defined, they can be used in different contexts. This re-arranging of classes is always better then re-implementing another class that is somewhat similar to an existing one, but not really. If implementing a first version of some new model, re-using existing code - even if this means less functionality or performance - is often much better, as less new code is implemented. Once a first version is working, start improving.
    • Even do this if the new logic seems to be more complex or complicated. Wrapper/helper code can often be written that abstracts this complexity away. At the same time, since existing "building blocks" were re-used, it is easier for new users to understand your code because they likely know the blocks your using from somewhere else already.
  • Typical programming "slogans" - NOTE: please do NEVER EVER blindly, strictly and without exception use those principles! NEVER!
    • DRY: don't repeat yourself! Re-arrange your code, or move parts into separate functions, before writing very similar code twice
    • SOLID → if you do want to produce code that is "good", you should definitely understand the principles described here (see below)
    • KISS: kind of a stupid one, "keep it simple and stupid", or "keep it simple, stupid". Wrong. Keep it as simple as appropriate, but if necessary, introduce the complexity you need.
  • Write "nice", "aesthetic" code, with a good flow and structure so others (and yourself after one month of not using your code) can actually read your code
    • Indentation! Never, ever don't do that properly, incredibly important to understand the logic of code (e.g. if/elseif/else blocks).
    • Use (several) newlines to group code blocks together
    • If helpful, use sections or comment headers for code blocks
    • Break up long code lines
    • When defining several variables, align the equal signs
  • Writing good code is just as important as implementing a good model. Too often, good models are implemented in sub-optimal code and therefore not understandable and usable by others.

SOLID

There's many guides about this. However, some of the authors seem to, here and there, not fully understand the principles themselves, so be aware of that.

For those who want to dive deeper into the matter here is fairly good video explaining the concepts

Good and Bad Examples

Try to implement code sections only once instead of copy and pasting them

% Instead of something like this:
if iType == 1
	fMassToAbsorb = this.oIn.oPhase.afMass(this.oMT.tiN2I.H2O);
	fAbsorbedMass = this.oOut.oPhase.afMass(this.oMT.tiN2I.H2O);

	fAbsorbedFlowRate = fMassToAbsorb * (fAbsorbedMass - fCapacity)/fCapacity;
elseif iType == 2
	fMassToAbsorb = this.oIn.oPhase.afMass(this.oMT.tiN2I.CO2);
	fAbsorbedMass = this.oOut.oPhase.afMass(this.oMT.tiN2I.CO2);

	fAbsorbedFlowRate = fMassToAbsorb * (fAbsorbedMass - fCapacity)/fCapacity;
end

% try to write the actual functions more general to use them only once.
% This saves time while maintaining your code and makes the code overall
% easier to read (shorter) and easier to understand
if iType == 1
	sSubstance = 'H2O';
elseif iType == 2
	sSubstance = 'CO2';
end

fMassToAbsorb = this.oIn.oPhase.afMass(this.oMT.tiN2I.(sSubstance));
fAbsorbedMass = this.oOut.oPhase.afMass(this.oMT.tiN2I.(sSubstance));

fAbsorbedFlowRate = fMassToAbsorb * (fAbsorbedMass - fCapacity)/fCapacity;

Stay in the V-HAB logic of the code! 

For example if you want to create a vector containing entries for different substances, the vector should be structured like the afMass/afPP vectors in V-HAB.
% Different logic than V-HAB
mfMassesToAbsorb(1) = this.oIn.oPhase.afMass(this.oMT.tiN2I.H2O);
mfMassesToAbsorb(2) = this.oIn.oPhase.afMass(this.oMT.tiN2I.CO2);


% Better version where the V-HAB logic is used
mfMassesToAbsorb = zeros(1, this.oMT.iSubstances);
mfMassesToAbsorb(this.oMT.tiN2I.H2O) = this.oIn.oPhase.afMass(this.oMT.tiN2I.H2O);
mfMassesToAbsorb(this.oMT.tiN2I.CO2) = this.oIn.oPhase.afMass(this.oMT.tiN2I.CO2);

Keep indentations!

There is a usefull button called "smart indent" which might help you with that! Also marking whole sections of code and pressing Tab to indent and Shift+Tab to dedent works!

% for example this code block is very hard to read:
for iStore = 1:10
	for iPhase = 1:5
mfValues(iStore,iPhase) = 1+1;
	if mfValues(iStore, iPhase) > 1
			mfValues(iStore, iPhase) = 1;
		end
end
end


% But if you mark it and use the smart indent option it will look like this:
for iStore = 1:10
    for iPhase = 1:5
        mfValues(iStore,iPhase) = 1+1;
        if mfValues(iStore, iPhase) > 1
            mfValues(iStore, iPhase) = 1;
        end
    end
end

Use spaces between calculation signs like = + - / * etc, it helps with readability (see example below)

% Without spaces the code is more difficult to read
fDensity=(varargin{1}.fPressure*varargin{1}.fMolarMass)/(this.Const.fUniversalGas*varargin{1}.fTemperature);

% With spaces it is better
fDensity = (varargin{1}.fPressure * varargin{1}.fMolarMass) / (this.Const.fUniversalGas * varargin{1}.fTemperature);

Use clear variable names!

fRH = fPPW / fVPW; 
% Is a lot harder to understand than:
fRelativeHumidity = fPartialPressureWater / fVaporPressureWater;

Also use clear loop variable names!

 For example:
for i = 1:10
	for l = 1:5
		for k = 1:20
			% some code
		end
	end
end


% is a lot harder to understand for anyone than if you use clearer loop variable names:
for iStore = 1:10
	for iPhase = 1:5
		for iCell = 1:20
			% some code
		end
	end
end

Align code blocks for readability:

% difficult to read
matter.branch(this, 'CCAA_Columbus_Output_2', {}, 'Columbus.Port2_From_CCAA', 'CCAA_Columbus_Output_2');
matter.branch(this, 'CCAA_Columbus_Output_3', {}, 'Columbus_CCAA_Water.Port_From_CCAA', 'CCAA_Columbus_Output_3');
matter.branch(this, 'CCAA_Columbus_CoolantIn', {}, 'ColumbusCoolantStore.Port_to_CCAA_Columbus', 'CCAA_Columbus_CoolantIn');


% easy to read
matter.branch(this, 'CCAA_Columbus_Output_2',   {}, 'Columbus.Port2_From_CCAA',                     'CCAA_Columbus_Output_2');
matter.branch(this, 'CCAA_Columbus_Output_3',   {}, 'Columbus_CCAA_Water.Port_From_CCAA',           'CCAA_Columbus_Output_3');
matter.branch(this, 'CCAA_Columbus_CoolantIn',  {}, 'ColumbusCoolantStore.Port_to_CCAA_Columbus',   'CCAA_Columbus_CoolantIn');

Try to reduce indentations to a minimum and to perform specific operations in the code only once (in this case the check for ~isfloat):

Functional but less structured code
for iProp = 1:length(csFieldNames)
	sField = csFieldNames{iProp};
	% If the current properties is any of the defined possible
    % properties the function will overwrite the value,
    % otherwise it will throw an error
    if any(strcmp(csFieldNames{iProp}, csPossibleFieldNames))
    	% checks the type of the input to ensure that the
    	% correct type is used.
    	xProperty = tTimeStepProperties.(csFieldNames{iProp});
        if strcmp(csFieldNames{iProp}, 'arMaxChange')
        	if ~isfloat(xProperty) || (length(xProperty) ~= this.oMT.iSubstances)
            	error('The arMaxChange value provided to the setTimeStepProperties function is not defined correctly. It either has the wrong length or is not a float')
			end
		else
			if ~isfloat(xProperty)
				error(['The ', csFieldNames{iProp},' value provided to the setTimeStepProperties function is not defined correctly as it is not a float'])
			end
		end
		this.(csFieldNames{iProp}) = tTimeStepProperties.(csFieldNames{iProp});
	else
		error(['The function setTimeStepProperties was provided the unknown input parameter: ', csFieldNames{iProp}, ' please view the help of the function for possible input parameters']);
	end
end
Code with opimized structure
for iProp = 1:length(csFieldNames)
	sField = csFieldNames{iProp};
	% If the current properties is any of the defined possible
    % properties the function will overwrite the value,
    % otherwise it will throw an error
	if ~any(strcmp(sField, csPossibleFieldNames))
		error(['The function setTimeStepProperties was provided the unknown input parameter: ', sField, ' please view the help of the function for possible input parameters']);
	end

	% checks the type of the input to ensure that the
	% correct type is used.
	xProperty = tTimeStepProperties.(sField);

	if ~isfloat(xProperty)
		error(['The ', sField,' value provided to the setTimeStepProperties function is not defined correctly as it is not a (scalar, or vector of) float']);
	end
	
	if strcmp(sField, 'arMaxChange') && (length(xProperty) ~= this.oMT.iSubstances)
		error('The arMaxChange value provided to the setTimeStepProperties function is not defined correctly. It has the wrong length');
	end

	this.(sField) = tTimeStepProperties.(sField);
end
  • Keine Stichwörter