Error when you don't set the block data type
TomLewis opened this issue · 19 comments
Ahoy!
Took me a while to figure out why I was getting internal errors;
When you use blocks broken, and you don't set a data parameter then youll get an internal error, It was either one of these, I cant remember, I think it was block 17.
blocks broken: 56;5;0 blocks broken2: 17;700;0
If you leave the 0 of one of those you'll get an internal server error when you do an /ar check
You should default to 0 if none is put in.
Hmm, strange; I don't seem to get that error. I used this config:
Test:
prerequisites:
world: world_nether
money: 1000
blocks broken: 56;100
requirements:
money: 10000
blocks broken: 50
results:
rank change: TestGroup2
command: broadcast &p has just completed path 'Default'!
command2: give &p 1 100
message: Congratulations, you completed the 'Default' path!
options:
display name: Test path
Worked fine for me. Maybe something else was incorrectly formatted?
Nope, those both seem to work fine. Well, if you encounter it again, make sure to report it!
I just want to make sure you are testing with stats3 and not statz, as thats what im using, I have statz installed just to hook into stats3.
Sailor:
prerequisites:
in group: 'Sailor'
time: 3d
requirements:
time: 3d
blocks placed: 10000
exp: 60
blocks broken: 56;5;0
blocks broken2: 17;700;0
players killed: 20
results:
rank change: Sailor;Carpenter
command: 'broadcast &p has just been promoted to Carpenter!; give &p 383:50 32; give &p 368 32; give &p 279 1; give &p 17 64; give &p 17:1 64; give &p 17:2 64; give &p 17:3 64; tell &p Gifted some goodies!'
message: 'Congratulations, you are now a Carpenter. See www.piratemc.com/ranks'
options:
display name: Carpenter
This is what I have.
I testing it with Statz. Please be advised that Autorank 4 will drop support for Stats 3 (as it is very buggy).
There was a few UI issues with Statz that never got fixed (Im PirateCraft on Spigot), and I wasnt sold on the database, you said you may chance it, and I cant be messing around with the insane amount of data I have, It took 2 DAYS to convert my Stats2 to Stats3, I have 3/4 years of data from 80,000 players, thats a lot of data, I only (This Wednesday) managed to get stats2 to convert to stats3, and even if I got all stats3 & autorank working by tonight, I still would loose 3 days of stats data from all the players, but I hit a brick wall when Autorank doesn't convert old data, its just ridiculous. Please don't drop Stats3 just yet, there is absolutely no reason to drop it, at all and it gives us server owners time to figure it all out, Statz isnt ready, it hasn't got a solid database schema, and its UI needs a lot of work (I posted in detail what it needs). Keep Stats3, Its not going to change any time soon, and then we can migrate to Statz when its more stable/mature and has a database schema this is 100% perfect. I DO NOT want to have to go through this utter mess of moving between database schemas again.
Hmm, I think I wrote a converter from Stats 3 to Statz. Is there any particular reason you do not want to use? You're free to reject of course. I agree somewhat that Statz database is not fully optimized yet, but Stats is not being updated any more. In my honest opinion, it seems buggy. I don't blame the author per se, it's also a mess because of the rewriting to Stats 3.
I'm curious, what would you like to see sorted before considering Statz a more mature plugin. I'm interested in making Statz as stable as possible of course.
I will write you up exactly what needs to be updated and changed to make Statz production ready, I need to focus on getting my actual server updated from Stats2 and an ancient version of autorank to Stats3 and the latest autorank first, I need to complete this step Im stuck, I cant even update my permissions system as im stuck on stats2. I need you to just let me use Stats3 for a bit, so I can get it converted from Stats2. I will write you a massive list, but my time is limited to very small pockets of time.
I'm curious, what would you like to see sorted before considering Statz a more mature plugin
From a technical point of view, it's apparent that the code base grew "organically" and it could use some restructuring.
@ljacqu While developing, I tried to keep it as organized as possible, but I'll see what I can do!
@ljacqu Maybe you could give some pointers to how it could be better organized?
I will create a massive list of what needs to be improved/added into Statz on the Statz Issue system once I have fully upgraded to Stats3 and a newer version of Autorank, Im very far behind and struggling with issues of Autorank working with Stats3 properly/Converting my Autorank data, im hoping V4 will fix all these.
Here we go!
Project setup
- The bin/ folder and Eclipse-specific files (like .project) don't belong in the repository -> they could be added to .gitignore
- The dependencies in lib/ could be imported from Maven repositories:
<!-- Essentials Repo -->
<repository>
<id>ess-repo</id>
<url>http://repo.ess3.net/content/groups/essentials</url>
</repository>
<!-- Vault Repo -->
<repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
</repository>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>Essentials</artifactId>
<version>2.13-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.milkbowl.vault</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.6</version>
</dependency>
Nothing wrong with keeping them as backup if the repository is down but typically you want to avoid using local folders whenever possible.
General findings
- Declare fields & variables with the interface type whenever possible, i.e.
List<String> list = new ArrayList<>();
andMap<Integer, String> map = new HashMap<>();
— and notArrayList<String> list = new ArrayList<>();
orHashMap<Integer, String> map = new HashMap<>();
. - Avoid large methods, this is by far the biggest technical problem in the codebase. For example CheckCommand#check, LeaderboardHandler#updateLeaderboard, PathBuilder#initialisePaths. Your package and class structure is really nice, however.
- Currently, you're forced to pass the
Autorank
class everywhere: the class is "too smart". Consider looking into dependency injection to get the specific classes you need. This allows for loose coupling and shows more clearly which classes a class depends on. - I'm really missing unit tests in this project and I'd gladly contribute some. Specifically, some of the more complicated util methods like parsing time strings would really benefit from tests, as well as tests with various configuration files to make sure everything is being handled correctly.
- Avoid repeating code: the handling of a time string is duplicated in GlobalAddCommand, GlobalSetCommand, RemoveCommand, AddCommand and SetCommand (found by searching the codebase for
contains("m")
😉).- Also within
MySQLManager
there's a lot of duplicate code for dealing with thoseFuture
s. In Java 8 you could create some nifty methods with some lambdas, but even with Java 7 I would urge you to consider extracting the common handling into separate methods.
- Also within
- Names of variables and methods could be improved to have more meaning (e.g. in
AddCommand
:int value
,StringBuilder builder
doesn't really tell me much about their purpose) - The JavaDoc you write is useful and coherent. Kudos on that. Only improvement I see there is to add JavaDoc to more non-obvious methods.
- However, there are a lot of "non-Javadoc" comment blocks on top of overridden methods—are those generated by default? I don't understand their point seeing as any reasonable IDE allows to see which method is being overridden or implemented.
- Remove the standard "// TODO Auto-generated method stub" comments from Eclipse throughout the code.
- There's a lot of commented out code scattered all over the place. I'd suggest to delete all of those occurrences. You have a versioning system (i.e. Git) so no code will be lost anyway.
- Logging - generally good and you typically log exceptions when they occur. That's fantastic. Just avoid
e.printStackTrace()
statements and use a logger method instead. - Consider introducing a constant
TICKS_PER_SECOND
andTICKS_PER_MINUTE
. It's hard to understand code like20 * 60 * PlaytimeManager.INTERVAL_MINUTES
(taken fromMySQLManager
) off the bat.
Specific issues
- AddOnManager is unused and its purpose is unclear. I would suggest to remove it until you know what sort of add-ons you want to support and who you want to cater to.
- BackupDataManager's
createNewFile()
method name is misleading and vague. - CommandsManager
- You can improve the efficiency of looking up the command drastically by first checking the
registeredCommands
map if it contains theargs[0]
. This requires having aMap<String, AutorankCommand>
instead of theMap<List<String>, AutorankCommand>
but I think it would be a more natural structure and you would have O(1) lookup time. Since you want to keep the order of the commands you can keep a separate list with them. Those are only stored references so the additional list won't take much size.
- You can improve the efficiency of looking up the command drastically by first checking the
- AutorankCommand and children
- Most of the
onTabComplete
implementations are just areturn null
. Why not implement that onAutorankCommand
directly? You can override it in the children whenever you really need to. - At a quick glance it appears that you always do
return true
to exit theonCommand
method. It would be simpler to change its signature tovoid
. Somewhere up in the call chain you need this return value, but you can havereturn true;
there once. - The description, usage and permission fields are defined by their setters being called inside of the constructor. This is a little strange as you have public setters for these fields, which should be immutable by intention. Also, if I extend this command I really need to pay attention to realize that I need to define these fields. You have three possibilities to improve this (by order of preference):
- Add
abstract String getDescription()
etc. onAutorankCommand
, which forces the implementors to define them. Remove the fields and the setters. - Add the three fields as constructor parameters to
AutorankCommand
, which also forces the implementors to pass it to the super constructor. - Minimally, the quickest way you can improve this a little is by changing the setters to have
protected
visibility.
- Add
- I was going to suggest using
List<String> args
instead ofString[] args
inonCommand
but I can't really find a place where it's a big inconvenience for you.
- Most of the
- Permissions
- Consider introducing an enum for the permissions. This will yield clearer method signatures and will avoid any typos. It's also immediately clear in the code which permissions exist, and their usages can be traced better.
- You have a trick to check if someone has a wildcard permission by doing something like
player.hasPermission("autorank.askdjaslkdj")
. I've seen it at least twice and given the initial surprise when seeing this, I would suggest extracting this into a util method somewhere.
- Configuration classes (me.armar.plugins.autorank.config package)
- InternalPropertiesConfig#hasTransferredUUIDs(boolean) does not make use of the boolean parameter
- SimpleYamlConfiguration#loadFile: You're catching exceptions just to rethrow them; this is redundant
- I see a lot of identical methods among PathsConfig, PlayerDataConfig and SettingsConfig. An abstract parent might be helpful here to implement
saveConfig()
,reloadConfig()
etc. only once. - Similar to the above point, in
Autorank#onEnable
you have to call an additional method right after instantiating the classes to load the files... Could this not be done in the constructor immediately? If you don't like that (there are valid reasons to want to avoid that), you could introduce astatic
method on those classes that will call the constructor and then the initializer method (createNewFile()
resp.loadFile()
). This gives you the most freedom while still making it simple to use the class for its regular purpose. getConfig()
in PathsConfig, SettingsConfig and PlayerDataConfig (and possibly elsewhere) has a redundant cast and literally behaves the same as if you just used theconfig
field directly (i.e.return this.getConfig().getBoolean("show warnings", true);
is 100% equivalent toreturn config.getBoolean("show warnings", true);
). More importantly,getConfig()
should beprivate
since it belongs to your class and isn't something you want to expose.PathsConfig#getRequirementValue
can be simplified to callinggetConfig().getString(pathName + ".prerequisites." + prereqName + ".value")
.FileConfiguration#getString
is null-safe and callsObject#toString
for you.- Same applies to
getResultOfRequirement()
andgetResultOfPath()
. - Minor:
SettingsConfig#getMySQLCredentials
and likely elsewhere: if youswitch
over an enum, it's good practice to throw an exception in thedefault:
clause so something fails loudly if an enum entry isn't handled. This way you're informed immediately in case you add an a new enum entry and forget to update the code in the switch. - It's apparent from the
List<String[]>
return value ofPathsConfig#getPrerequisiteOptions
that there's a better way to handle it. I need to be more familiar with this plugin to make a suggestion, though.
- The data package
FlatFileManager#loadDataFiles
should not be public. But actually the entire method should be removed because it initializesstatic
fields which should be immutable! Consider using Guava'sImmutableMap
.- Alternatively, you can add the file value directly on the
TimeType
enum. You can introduce a method likegetFile()
and you'll have nice, readable code. ButTimeType
has quite a general usage so I wouldn't necessarily recommend putting something this specific on the enum directly.
- Alternatively, you can add the file value directly on the
FlatFileManager.TimeType
is used in so many places that it should be in its own class. It doesn't strictly belong toFlatFileManager
.- In
GrabAllTimesTask
:⚠️ avoid usingSELECT *
if you're only interested in a few columns. Especially because you access what you want withrs.getString(1)
. This is code that will silently break someday.- there's a lot of cases in which the empty map
times
will be returned. This is a little cryptic to me. I see similar code where it's being called,MySQLManager#getAllPlayersFromDatabase
. Might be that code was moved around and there are some unintended leftovers? If not, these sections would benefit of explanatory comments and maybe if you returnedCollections.emptyMap()
it's visible more clearly that an empty map is being returned.
GrabDatabaseTimeTask
- I would suggest to rename
GrabDatabaseTimeTask
toGrabPlayerTimeTask
. This has a better contrast withGrabAllTimesTask
, which also gets times from the database. ⚠️ Same as inGrabAllTimesTask
, avoidSELECT *
and then fetching some specific column. You can simply runSELECT time
or whatever the column name is.
- I would suggest to rename
- In the future if anyone has a huge database and complains that it's slow, you should look into creating indexes on that data table, especially on the
uuid
column. - Be sure to always close
Statement
s andResultSet
s inSQLDataStorage
.
- Debugger: really cool idea! The
DateFormat
fields shouldn't be static as the class isn't thread safe. - DependencyManager
- The
Map handlers
is almost unnecessary. You go through a lot of hoops to always usegetDependencyHandler(Dependency.ESSENTIALS)
etc. instead of just maintaining four separate fields in the class. DependencyHandler#get()
is never used and can be removed from the interface. I don't think you want to allow other code to just get the plugin instance. That's whatDependencyHandler
andDependencyManager
do—they wrap the specific plugins and offer a stable API for the other classes to use so they don't need to deal with third-party plugins.
- The
- Language handling
- A static field doing I/O on an enum (
FileConfiguration LANG
inLang
) isn't nice. It's working out OK for you right now but it will bite you in the ass whenever you'll want to add more specific behavior, like user specific languages. You already have aLanguageHandler
which is looking quite good; you'd just have to transfer more responsibilities to that class. :) - I wonder how efficient
Lang#getConfigValue
is and whether there's a good improvement for it.
- A static field doing I/O on an enum (
- LeaderboardHandler
boolean order
insortByComparator
should be calledascending
.String layout
,double validTime
andint leaderboardLength
should all bestatic final
.- Avoid using the slower
String#replaceAll
if you're not using regular expressions (you're not); useString#replace
instead.
- pathbuilder package & subpackages
- Requirement
- Specify in the
Requirement
javadoc that the implementing classes must have a no-args constructor (as required by the RequirementBuilder class). getDependencyManager()
and similar are really bad interface design. At the minimum they should beprotected
and theRequirementBuilder
should be responsible for invokingRequirement#setAutorank
. I expect a fully usableRequirement
being returned fromRequirementBuilder
.setOptions
has no business throwing aNoClassDefFoundError
.
- Specify in the
RequirementsHolder
⚠️ MethodgetDifferenceIndex
- Avoid catching
IndexOutOfBoundsException
- prevent it from ever being thrown in the method - The return value is used in
String#substring
but it defaults to-1
.string.substring(-1)
throws an exception...
- Avoid catching
- Method
meetsRequirement
- Use more appropriate variable name for
uuid
- it's the UUID of what? - Some variable scopes are too broad;
boolean result
is declared way higher than when it is used for the first time; idem forint reqId
.
- Use more appropriate variable name for
Requirement
implementations- Many classes have package-private variables; they should be
private
. - Many classes have a check like
if (achievementCount != -1)
where it should maybe beif (count < 0)
? - LocationRequirement: Calculations are done in
meetsRequirement
which are discarded if the worlds don't match. The world check could be done before the calculations. - An additional abstract class extending Requirement may make sense; a lot of classes retrieve some value and compare it to a predefined value. Apart from "getting some value" the rest of the logic appears to be the same among many classes.
- Many classes have package-private variables; they should be
Result
could use some Javadoc. Can't really say much more about it right now.- PathManager
PathManager#matchPathbyDisplayName
'sisCaseSensitive
parameter is alwaysfalse
; you can drop it and remove the case-sensitive implementation.- Similarly,
PathManager#matchPathbyInternalName
hasisCaseSensitive = true
only in one usage of four—is this intended? setBuilder
ispublic
but only used within the constructor. Replace withthis.builder = new PathBuilder(plugin));
and remove method.
- Requirement
- Permissions
- 3 out of 4 implementations have to convert a collection to an array for
String[] getGroups()
instead of having the method return aCollection<String>
. Change method signature toCollection<String>
(orList<String>
/Set<String>
if you fancy that more). - GroupManagerHandler
- Methods
getGroup()
,getPrefix()
,getSuffix()
are unused. getGroups()
: Iterate overCollection<Group> worldGroup
directly instead of creating an intermediateList<Group> list
. Also you put all of the group's names intoList<String> groups
to then convert it manually to aString[]
array. Simplify to filling an array (or collection, see point above) immediately.- At a quick glance, the same applies to all the other methods returning
String[]
.
- Methods
PermissionsPluginManager
: Rename methodsfindGroupManager
etc. to something likeisGroupManagerAvailable
to give meaning to theboolean
return type.- Additionally, since only used within PermissionsPluginManager, they should be
private
.
- Additionally, since only used within PermissionsPluginManager, they should be
- 3 out of 4 implementations have to convert a collection to an array for
- PlayerChecker#getMetRequirementsHolders is spaghetti code; it's a mix of long conditional clauses and
continue
statements and needs restructuring. - Handlers
- Rename
DummyHandler
toNoOpHandler
orEmptyHandler
or something. Dummy makes it sound like a temporary or test implementation. - Rename the
statTypes
enum inStatsPlugin
toStatType
to conform to Java conventions. StatsPlugin#getNormalStat
should have aMap<String, Object>
param—notHashMap<String, Object>
.- StatsPluginManager's
find*
methods have the same issue as the methods inPermissionsPluginManager
(see point there) - Once I understand the plugin a little better I may have a better suggestion for the
HashMap<String, Object> arguments
parameter ingetNormalStat()
. I see a lot ofparameters.get("...").toString()
in there, and stuff like(Integer) arguments.get("moveType")
also suggests to me there's a better way of doing this. I can't make a suggestion yet, though.
- Rename
- I skipped the
updater
andutil.uuid
packages - AutorankTools
getCorrectReqName
- The
static List<String> reqTypes
field should be aSet
. This way inregisterRequirement(String)
you can simply callregTypes.add()
without checking withcontains()
first. ⚠️ The loop ingetCorrectReqName()
is nothing else thanif (regTypes.contains(oldName)) return oldName
... Please simplify.AutorankTools
should be a stateless utility class and should not be aware of any "application data." ThegetCorrectReqName
method should be moved to some class that is more specific where it makes sense for it to know about the existing requirements.
- The
getCorrectResName
- all of the points forgetCorrectReqName
apply for this too.largestK
- Change signature from
Integer[]
toList<Integer>
. You only call the method in one place—a place where you have to convert the List to an array to fulfill the method signature. catch (ArrayIndexOutOfBoundsException e)
is a bad idea. A simple check at the beginning could prevent this. An exception like this should never be caught.
- Change signature from
sendColoredMessage(Player, String)
is redundant asPlayer
is aCommandSender
, and a methodsendColoredMessage(CommandSender, String)
exists doing the same thing.makeStatsInfo(Object...)
- needs a more specific name
- if the number of arguments is odd, you'll get an array out of bounds exception
- I don't think it's intuitive that it will filter out negative numbers if they just so happen to be valid numbers. I think the entire method needs to be revised. After looking at the usages more carefully I can provide a suggestion.
makeProgressString
,getStringFromSplitString
are unused -> delete?getFoodName
: the simple cases can be concatenated and thenitem.name()
can be returned.getFoodItemFromName
has comment "Cannot use switch, is only supported in Java 1.7". This is outdated. Use switch. ;)findClosestSuggestion
could benefit from returning an inner class consisting of adistance
andsuggestion
field instead of data concatenated into one String.createStringFromList
: not necessary to create an array, just increment ani
counter manually.isExcluded(Player)
: I have no idea what this check does without checking the implementation. Rename toisExcludedFromRanking
?stringtoDouble
,stringtoInt
:to
should beTo
; both methods don't throw aNumberFormatException
. Remove from signature.
- ValidateHandler
- I feel like
List<String> permGroups
should be called something else andString[] vaultGroups
should bepermGroups
? String[] vaultGroups
is only used wayyy at the bottom of the method. Declare it there.
- I feel like
- WarningManager
- The javadoc on the class says something about the messages being stored as
"warning message>10"
(i.e. a split with>
). This no longer seems to be the case. registerWarning(String message, int priority)
is only ever called withpriority = 10
. This leaves you with two choices:- Drop the
priority
parameter. - Simplify to a
Priority
enum instead, having three values. You have to currently maintain code to ensure a priority between 1 and 10 but don't have a use for it.
- Drop the
- The javadoc on the class says something about the messages being stored as
- Autorank main class
initializeReqsAndRes
doesn't belong in the main class. This needs some sort of RequirementFactory class or similar.- The main class should probably not be concerned about logging (looking at
debugMessage(String)
). The small method isn't a bother in the code there but it makes all classes depend on your main plugin file to debug messages, which tightly couples everything. - It's a really cool idea to offer
public
setters and getters for all of your main 'services' (looking atsetLeaderboardManager
,setPathsConfig
etc. Those types should probably all be interfaces, though, since sometimes it's a little hairy to have to extend existing classes that are quite specific (I'd need to call thesuper(Autorank)
constructor, and I have no way of avoiding code to be executed that is done within the constructor). - Discourage the use of
Autorank#getAutorank
; plugin developers should just do what that method does directly.
Wow. I was blown away by your post! Many thanks for all the suggestions, you are so right about most of it. As you've said a few times in your post, making suggestions is hard when you're not 'too familiar' with the code, but you did a really good job; I certainly have some work to do.
You also mentioned something about unit tests. I know what they are, but have never written any nor tested with it. Maybe you could provide a few (as well as the infrastructure needed for it to be used)?
I claim to be no expert programmer, but it's good to get some proper feedback about my codebase, it doesn't happen that often ;-) You mentioned some new concepts that I'm currently not familiar with (for example Dependency Injection). Let's hope I can grasp these concepts properly and am able to implement them. If you feel like providing suggestions or contribute to the codebase, you are more than welcome to!
Thanks a bunch and I'll get coding!
PS. I've saved this post for offline reading (if you don't mind), so I can come back to it.
@ljacqu Holy crap that post! the detail is amazing! Seriously thank you for writing that in detail, it means a lot to me to get a solid statistics recording plugin that is awesome, I rely on it! having a backbone breakdown like you have just posted is utterly amazing.
@FrozenBeard I'm still eagerly awaiting your suggestions for new (and probably) better features :)
@Staartvin They will come! I need to still get myself off stats2, to stats3 and the new Autorank (Gotta find the time to test with V4!) Then once this is stable and in production, I will test a convert to Statz and run it through its paces! I already have a rough list of ideas, but I want to give you a solid list!
Thanks for the positive feedback, @Staartvin @FrozenBeard. I didn't know what reaction this would get since it's a touchy situation to pop out of the blue with a wall of text. 😄
making suggestions is hard when you're not 'too familiar' with the code
Yeah, I don't think it's OK to be like "this is not good" and not to offer suggestions for improvement. You can ask me for clarifications or suggestions for any of the points I brought up. Might just take a while for some of the bigger stuff for me to get familiar more :3
unit tests. [...] Maybe you could provide a few (as well as the infrastructure needed for it to be used)?
Absolutely! Testing is my thing so you'll have a PR coming your way. I help maintain AuthMe and the project has 832 unit tests right now... :P
Dependency Injection). [...] If you feel like providing suggestions or contribute to the codebase, you are more than welcome to!
Thanks! Dependency injection is another thing that was introduced into AuthMe and the solution for it eventually became a separate project that is now used by AuthMe, PerWorldInventory & voxelwind. Guice is another option. I can give a detailed comparison, and just for the record I'm not trying to force "mine" onto this project (no matter how this looks lol). (In short: Guice is a standard framework people will be familiar with and has more features; the linked injector is smaller in file size, defaults to wiring up singletons and you can @ me anytime for support and feature requests.)
I've saved this post for offline reading (if you don't mind)
I'm just really happy that my post is noticed—please, do whatever you want with it.