What field set Salesforce is useful for?
According to the Salesforce documentation
https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_dynamic_vf_field_sets.htm
fieldsets are a mere UI feature, although they can be accessed by Apex code too. Even the value of using them on the UI is not obvious to me, but Andrew Fawcett in his Force.com enterprise architecture mention them as an important feature:
What are really compelling use cases for field sets on the UI side? Are there things that you can't do without field sets or that are much more complicated?
Is there any value in using them when not using them in custom visualforce / lightning pages?
The big thing about fieldsets Salesforce is that they're declarative. If you define a fieldset, and use that in visualforce/apex, you can change the fields that are displayed in visualforce, or processed in apex, without modifying code. This also means that, in my org, I can (safely) change behaviour in production without the need for a deployment.
Probably the best example I personally have of this is the custom "mass edit" tool that I developed for my org. From an Opportunity, my users can click a button that brings them to a visualforce page. There, they can select the OpportunityLineItems that they want to edit. From there, my fieldset determines what fields they are allowed to edit en masse.
If I want to change the fields available for editing, I simply add/remove things from the fieldset (my unit tests for that code also use the fieldset).
Outside of visualforce, fieldsets can also be used if you want to roll your own declarative rollup summary tool. Something like
update oppsToUpdate;List rollupFields = new List();for(Schema.FieldSetMember f : SObjectType.OpportunityLineItem.FieldSets.OliRollupFields.getFields()) { // To use fields from a fieldset in a query, we need to get the field path // (which returns the api name of the field) rollupFields.add(f.getFieldPath());}// String.join() allows us to easily take a list of strings, and put commas between them.// Using String.join(), we don't need to worry about extra comma(s) at the beginning or end.// String.join() isn't enough to put the field names in between the parenthesis// of 'SUM()', so we need to use replaceAll() to take care of that.// The regex matches valid field names (alphanumeric + underscores) and stores them// in captured group 1.// In the 'replace' string, $1 is substituted with the value stored in that numbered// captured group.// 'a_field__c, b_field__c' would become 'SUM(a_field__c) a_field__c, SUM(b_field__c) b_field__cString aggFunctions = String.join(rollupFields, ', ').replaceAll('([a-zA-Z0-9_]+(__c)?)', 'SUM($1) $1');String queryString = 'SELECT OpportunityId, ' + aggFunctions + ' FROM OpportunityLineItem WHERE OpportunityId IN ppIds GROUP BY OpportunityId';List oppsToUpdate = new List();for(AggregateResult ar atabase.query(queryString)){ Opportunity tempOpp = new Opportunity(Id = (Id)ar.get('OpportunityId')); for(String field :rollupFields){ tempOpp.put(field, (Decimal)ar.get(field) == null ? 0 : (Decimal)ar.get(field)); } oppsToUpdate.add(tempOpp);}