Comments (12)
Hey Aiden, here's a breakdown of the first refactoring actions. After these we'll be able to apply a second layer of refactoring for decreasing coupling and increasing coherence throughout the codebase.
1) Sticking to a given code style
The first refactoring should be on code style consistency. We had already built ESLint into the project, so every new commit should make sure it's consistent with the style. You can activate it by running npm lint
and it will pour out a list of errors. ESLint also comes with automatic fixing of a lot of errors, and I made it available as another npm script. If you run npm run lint-fix
, it will fix most of the styling issues that can be automatically fixed.
In order to streamline this, we can also build in a commit hook to make sure it will execute npm run lint-fix
before anyone commits any code.
Most code editors today also support automatic linting — they can validate and fix your code using ESLint on every save. This is actually pretty useful, and a setting we use in Visual Studio Code all the time.
The biggest surprise element here could be that you are using tabs in your editor. While it's a never-ending debate whether to use tabs or spaces, the JavaScript community is leaning more towards two-character spaces. This is also reflected in the built-in ESLint config. Therefore I would appreciate if you made the switch.
Currently there are 812 problems in the codebase, and most of them are in fact about uninitialized variables. These needs to be cleared out, and the project should pass with 0 errors on an ESLint run.
2) Sticking to ES6/7
This is a multi-part refactoring. First, ES6 brought in const
and let
, and it's recommended to replace var
with a proper use of these. Furthermore, it's good to make use of arrow functions as well. Further constructs like object destructuring, default parameters and rest parameters could also be applied.
3) Removing async library
As I have noted earlier, currently the code is still single-threaded. async
library by default doesn't incorporate any parallelism. In fact, the codebase also doesn't need any at this point. Most of the operations are either very straightforward arithmetic operations or are operations on very simple data structures like small arrays and objects. In any case, in a multi-threaded operation, the overhead caused by context switching and object serialization / deserialization would destroy any benefits parallelism would bring.
At this point, it would make sense to remove all of the async calls with regular inline JavaScript.
4) Replacing promises with async/await
The code makes heavy use of promises, and in fact this reduces readability. The idea of promises was to avoid having callback hell — increasing indentation levels in async code — but that's exactly the case here even though the codebase is using promises.
Again I am at a split here. All of the functions are synchronous and it makes sense for them to be synchronous. In the future if the codebase gets 20x the size, this might be a very slight convenience and we may want to turn some functions into microservices — but now with async/await it's extremely straightforward.
So I would suggest to remove all the promises and async/await from the codebase at this point and make everything actually what they are — synchronous —, but it's your call. If you want to keep this approach though, I recommend to replace all the promises with async/await.
5) Multiple returns, early returns
The codebase makes heavy use of nested if/else clauses and reduces readability and maintainability. A careful rewrite can remove most of this by making use of multiple, early returns. Wherever one sees a resolve
in the current codebase, one should replace it with return resolve
. Simply because of the fact that a resolve
in a Promise is already the end of the execution of that function.
This removes the need to use else
clauses after resolve
, because;
if (a) {
resolve(b)
}
else {
resolve(c)
}
is practically the same as;
if (a) {
return resolve(b)
}
return resolve(c)
Making early returns also saves the reader from the trouble of having to read (and understand) the whole function, when the part of the execution they are interested in already actually is over.
6) Removing duplication
There's a pattern of code duplication throughout the codebase, and this should be eliminated to increase readability and maintainability.
The following code;
if (playerProximity[0] < 10 && playerProximity[1] < 10) {
possibleActions = populatePossibleActions(possibleActions, 0, 20, 30, 20, 0, 0, 20, 0, 0, 0, 10)
resolve(possibleActions)
} else if (player.skill.shooting > 85) {
possibleActions = populatePossibleActions(possibleActions, 60, 10, 10, 0, 0, 0, 20, 0, 0, 0, 0)
resolve(possibleActions)
} else if (player.position === 'LM' || player.position === 'CM' || player.position === 'RM') {
possibleActions = populatePossibleActions(possibleActions, 0, 10, 10, 10, 0, 0, 0, 30, 40, 0, 0)
resolve(possibleActions)
} else if (player.position === 'ST') {
possibleActions = populatePossibleActions(possibleActions, 0, 0, 0, 0, 0, 0, 0, 50, 50, 0, 0)
resolve(possibleActions)
} else {
possibleActions = populatePossibleActions(possibleActions, 10, 10, 10, 10, 0, 0, 0, 30, 20, 0, 10)
resolve(possibleActions)
}
can be written as;
let parameters = []
if (playerProximity[0] < 10 && playerProximity[1] < 10) {
parameters = [0, 20, 30, 20, 0, 0, 20, 0, 0, 0, 10]
} else if (player.skill.shooting > 85) {
parameters = [60, 10, 10, 0, 0, 0, 20, 0, 0, 0, 0]
} else if (['LM', 'CM', 'RM'].includes(player.position)) {
parameters = [0, 10, 10, 10, 0, 0, 0, 30, 40, 0, 0]
} else if (player.position === 'ST') {
parameters = [0, 0, 0, 0, 0, 0, 0, 50, 50, 0, 0]
} else {
parameters = [10, 10, 10, 10, 0, 0, 0, 30, 20, 0, 10]
}
resolve(populatePossibleActions(possibleActions, ...parameters))
This is not only a 36% reduction in character count, but it's also much easier to read. It's also a lot easier to refactor when you want to change how populatePossibleActions
work, for example.
Conclusion
These are the first layers of refactoring that the codebase needs. The first focus should be on increasing style consistency, reducing duplication and thereby increasing readability and maintainability.
Then we will be able to apply certain design patterns like strategy, factory, command, chain of responsibility, and further object oriented programming patterns like polymorphism and other patterns like composition.
Hope this helps.
from footballsimulationengine.
Here's another example. The following;
if (thatTeamPlayer) {
if (thatTeamPlayer.startPOS[0] === thisPos[0] && thatTeamPlayer.startPOS[1] === thisPos[1]) {
if (!deflectionPlayer) {
if (thisPos[2] < thatTeamPlayer.skill.jumping && thisPos[2] < 49) {
deflectionPlayer = thatTeamPlayer
deflectionPosition = thisPos
deflectionTeam = opposition.name
thisPosCallback()
} else {
thisPosCallback()
}
} else {
thisPosCallback()
}
} else {
thisPosCallback()
}
} else {
thisPosCallback()
}
can be rewritten as;
if (!thatTeamPlayer) return thisPosCallback()
if (!(thatTeamPlayer.startPOS[0] === thisPos[0] && thatTeamPlayer.startPOS[1] === thisPos[1])) return thisPosCallback()
if (deflectionPlayer) return thisPosCallback()
if (thisPos[2] >= thatTeamPlayer.skill.jumping || thisPos[2] >= 49) return thisPosCallback()
deflectionPlayer = thatTeamPlayer
deflectionPosition = thisPos
deflectionTeam = opposition.name
thisPosCallback()
from footballsimulationengine.
@dashersw - could you write out a list of refactoring tasks and I'll start actioning them
from footballsimulationengine.
This is exactly what I was looking for, great depth to the comments and the detail means I can start the refactoring as my next task. Thanks! :)
from footballsimulationengine.
@dashersw, I've completed the above refactoring changes. (Stage 1). The only remaining lint issues are as follows:
footballSimulationEngine/lib/playerMovement.js
60:16 error Unexpected `await` inside a loop no-await-in-loop
81:24 error Unexpected `await` inside a loop no-await-in-loop
93:15 error Unexpected `await` inside a loop no-await-in-loop
110:13 error Unexpected `await` inside a loop no-await-in-loop
119:22 error Unexpected `await` inside a loop no-await-in-loop
131:13 error Unexpected `await` inside a loop no-await-in-loop
148:11 error Unexpected `await` inside a loop no-await-in-loop
170:29 error Unexpected `await` inside a loop no-await-in-loop
179:29 error Unexpected `await` inside a loop no-await-in-loop
189:29 error Unexpected `await` inside a loop no-await-in-loop
198:29 error Unexpected `await` inside a loop no-await-in-loop
Any thoughts about changing the code to facilitate the lint errors?
from footballsimulationengine.
@GallagherAiden I've went through your changes, great work as always! How about removing async/await from these pieces in the code? I still don't believe this engine will ever require an async operation.
from footballsimulationengine.
It requires async/await in the decideMovement function unfortunately. I think I'll have to rework the whole function to remove them. I'll keep playing because I think I should be able to sync the whole simulator.
Feel free to add more refactoring tips and I'll work them in at the same time
from footballsimulationengine.
Two examples;
For async/await you can make use of the try / catch blocks;
let team1 = await common.readFile(t1)
.catch(function(err) {
throw err.stack
})
let team2 = await common.readFile(t2)
.catch(function(err) {
throw err.stack
})
let pitch = await common.readFile(p)
.catch(function(err) {
throw err.stack
})
let matchSetup = engine.initiateGame(team1, team2, pitch)
.catch(function(err) {
throw err.stack
})
return matchSetup
can be rewritten as;
try {
let team1 = await common.readFile(t1)
let team2 = await common.readFile(t2)
let pitch = await common.readFile(p)
let matchSetup = engine.initiateGame(team1, team2, pitch)
return matchSetup
} catch (e) {
throw e
}
Also,
const error = 'Please provide two teams and a pitch JSON'
throw error
can be reduced down to
throw new Error('Please provide two teams and a pitch JSON')
new Error
is important because it's what gives you the stack.
from footballsimulationengine.
if (direction === 'wait') {
newPosition[0] = position[0] + common.getRandomNumber(0, (power / 2))
newPosition[1] = position[1] + common.getRandomNumber(0, (power / 2))
} else if (direction === 'east') {
newPosition[0] = position[0] + common.getRandomNumber((power / 2), power)
newPosition[1] = position[1] + common.getRandomNumber(-20, 20)
} else if (direction === 'west') {
newPosition[0] = common.getRandomNumber(position[0] - 120, position[0])
newPosition[1] = common.getRandomNumber(position[1] - 30, position[1] + 30)
} else if (direction === 'south') {
newPosition[0] = position[0] + common.getRandomNumber(-20, 20)
newPosition[1] = position[1] + common.getRandomNumber((power / 2), power)
} else if (direction === 'southeast') {
newPosition[0] = position[0] + common.getRandomNumber(0, (power / 2))
newPosition[1] = position[1] + common.getRandomNumber((power / 2), power)
} else if (direction === 'southwest') {
newPosition[0] = position[0] + common.getRandomNumber(-(power / 2), 0)
newPosition[1] = position[1] + common.getRandomNumber((power / 2), power)
}
This part could also benefit from reduction — again mostly what changes here is the parameters to common.getRandomNumber
from footballsimulationengine.
There's one more emerging pattern in the catch blocks, like
catch (error) {
throw new Error(error)
}
If you don't do any other operation in the catch block than to re-throw the error, you don't need a try-catch block at all, since the behavior is exactly the same as not having it.
from footballsimulationengine.
hi @dashersw, sorry for the delay in responses, I had some time away from changing the code. There seems to be more traffic so making wider changes for improvements in two phases. 2.2.0 and eventually 3.0.0 (where v3 will be high impact changes to the general way the game works).
I think I've addressed the catch block issues as discussed in your last comment. The only part I couldn't figure out how to properly improve is #6 (comment)
Any thoughts? Any other changes I can make to modernise the code?
from footballsimulationengine.
Closing for now, but any other changes you think will help with modernisation please let me know
from footballsimulationengine.
Related Issues (20)
- Corners are sometimes incorrectly given to the wrong team HOT 1
- Player Rating HOT 1
- Players on opposing teams cannot have the same name HOT 2
- Not returning a ball position when resolving deflections HOT 1
- Corner Improvements HOT 1
- Functionalise getting the penalty box boundaries HOT 1
- Goal Scored iteration log doesn't say which player scored HOT 1
- Error setting ball position after deflection HOT 1
- intent position showing as undefined HOT 1
- add goals to players statistics HOT 1
- Players not on pitch after free kick setup HOT 1
- Player variable undefined HOT 1
- highNumb somethings is float HOT 5
- Is this project still active? HOT 1
- Occasionally only one player runs towards the ball HOT 2
- AutoGoal HOT 1
- Find Index with player name HOT 1
- ayyg
- Players return to pitch after red card HOT 1
- In iteration JSON I have observed the ball position last index sometime goes missing
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from footballsimulationengine.