the word “cleanly” gets subjective quickly, and for the most part comes down to “readable”, a person that has never seen the versatility of a given language might look at something like:
FRotator DRotation = UKismetMathLibrary::FindLookAtRotation(FollowCamera->GetComponentLocation(), GetActorLocation()); FollowCamera->SetWorldRotation(FMath::RInterpConstantTo(FollowCamera->GetComponentRotation(), DRotation, DeltaTime, CamRotationSpeed));
and be completely baffled, or something involving a Lambda function (where a function is conjured into existence as an argument to another function being called instead of sending an existing function to be called, or just a pointer to an object that a specific function would be called on)
while a highly experienced programmer in a language might still be completely lost by something they themselves wrote the week before because they forgot the tiny intricacies of each member and function being called.
the “Clean Code” Paradigm is about making functions about MyDesciptiveObject.DoThing1(OnlyArg).DoThing2();
which is often abstraction on abstraction where those 2 function calls could be doing 500 magic steps, and we just trust the outcome. When it comes to refactoring, or optimizing these abstractions can create so many knock on effects or edge cases, you either end up rewriting the function, or the function and all the places it is called from. Though clean code often necessitates a tight weaving between the definitions and the implementation, and as systems become more generic we need more information/arguments to actually do the task. “Clean Code” also is very spoken/written language dependent, and potentially dependent on the writer MyObject->GetX()
is Clean code, but without a meaningful name on the Object being referenced/de-referenced GetX()
might just be ambiguous.
the answer to this is often “write good comments, and read the comments” but comments are not always a good answer either because we make arbitrary assumptions based on our own understanding in the moment we write the lines as to what is needed to be known.
Then many programmers will tend to write comments in their native language, or the primary language of the team working on it. Unreal is written by Prominently English speakers, a large majority of the user base are English Speakers, and even C++ many of the keywords of the language are in English (while reserving several special characters found in other spoken languages for specific meanings in the programming language, so they can not really be used in Variable names).
The Comments of Unreal Engine’s source code are in English meaning that there is a good chance of a spoken/reading language gap for “just read the comments”. I do not envy the situation of needing to pull out a translator/interpreter to read a singular comment to a function or line of code which are often incomplete sentences/thoughts that might not even map well to the other language, and this is if they are not just a bunch of words mashed together into a “descriptive name” (which I am sure a good portion of Translators/Interpreters just throw up all over)
for the example line of code you brought up:
ClockHandle->ResumClock(this, ClockHandle);
- the first
ClockHandle
is to get the primary object context, though sometimes this is also done to get fast access to the class members regardless or the actual object the function is being called from, or to do specific work on or based on the object itself.
ResumeClock()
is a member function of the class ClockHandle
belongs to (the arrow operator tells us ClockHandle
is a pointer, or at the very least is being treated as a pointer in this instance)
- “
this
” is a pointer (rarely is this*
actually passed as a reference though it is possible) to the current object we are inside of, which is often used as a means of ownership, and/or callback if something needs to be done asynchronously or the receiving function may need to have access to the members of the caller. Where you called out this being a Blueprint node these often have a const UObject* WorldContext
accepting a this*
to ensure Garbage Collection doesn’t do unexpected or bad things in the middle, and to do things reserved to the things ‘knowing about’ the UWorld
- The second
ClockHandle
which given that in the line was accessed as a pointer, and is not being de-referenced is being passed as a pointer to the function (we don’t get to see if there is any const
in the function call, and it is really being passed as a *&
which is a special way of passing an object that gets a bit technical), so this is either being used as a “resume from the value this once has” or “resume this one based on the one being called from”
passing a this*
is not outlandish, and in Unreal C++ is rather common (especially for Blueprint nodes).
several of the function calls get away from “Clean Code” because of a near Generic implementation or they need to take in a lot of data, or even return a lot of data back through arguments passed by pointers, references, and Reference-to-Pointer. you also need to realize that many functions/events in blueprints are actually simplified for the user, like many blueprint users might be surprised to find PrintString()
actually has 5 other arguments beyond the string itself, has no direct C++ equivalent, and when technically called in C++
// this is the fewest arguments this line of code gets.
GEngine->AddOnScreenDebugMessage( int32, float, FColor, FString)
while many of the functions used in blueprints will take a secret UObject*
which just silently get called with this*
behind the scenes (mainly to cut down of spaghetti noodles)
I don’t speak the language of the guide you are showing, so I am not sure if they make the point but: there are things that blueprints, and blueprint graph is more suited to doing
- dealing with the Content Browser/Drawer to avoid Runtime-Resolved-Hard-String-References that can silently fail because something was renamed/moved or the spelling was off to begin with.
- State Machines (animation and “AI” especially if you need fuzzy logic)
- things dealing with timelines or absolute relative positioning
you can use the derived blueprint as a means for data capture into C++